Tkinter.PhotoImage data for dynamic CGI .gif without writing to file?

Bengt Richter bokr at oz.net
Sat Dec 28 11:32:06 EST 2002


On 28 Dec 2002 02:43:32 -0800, hwlgw at hotmail.com (Will Stuyvesant) wrote:

>[Bengt Richter]
>> Maybe the StringIO module will help you. I.e., use an instance instead of a file).
>
>I have been using StringIO and friends for redirecting sys.stdout
>etcetera.  But in this case there is a Tkinter.PhotoImage.write()
>method and I have no idea how to redirect it.  As far as I can tell it
>wants a string parameter that is a filename.

<note>
The immediately following won't work with Tkinter.py, which seems to delegate
the i/o to something that ignores Python's file or open feature. I tried it with both rebound.
But since I did it maybe I'll leave this in as an example of bad emergency hacking ;-)

OTOH, the PIL version does allow making a gif without writing a file, if you create the
right file object -- see below.
</note>

<ignorable>
As an emergency hack, you could try intercepting __builtins__.file (and maybe
also open, which I didn't add. This is assuming they are used to get file/open
dynamically.

 >>> class CatchWrite(object):
 ...     def __init__(self): self.data = []
 ...     def write(self, s): self.data.append(s)
 ...     def close(self): return # need this since plain stringio might get closed
 ...
 >>> class FileHook(object):
 ...     def __init__(self, oldfile, nametoredirect = None, stringfile=None):
 ...         self.oldfile = oldfile
 ...         self.stringfile = stringfile
 ...         self.nametoredirect = nametoredirect
 ...     def __call__(self, *args):
 ...         print 'FileHook%s' % `args`
 ...         if args and args[0]==self.nametoredirect: return self.stringfile
 ...         return self.oldfile(*args)
 ...
 >>> gifdata = CatchWrite()
 >>> __builtins__.file = FileHook(__builtins__.file, 'xx.gif', gifdata)

Make sure it doesn't interfere with other-named files
 >>> fxxx = file('xxx','w')
 FileHook('xxx', 'w')
 >>> fxxx.write('test write to fxxx\n')
 >>> fxxx.close()

Read it back
 >>> file('xxx').read()
 FileHook('xxx',)
 'test write to fxxx\n'

Check our data catcher
 >>> gifdata.data
 []

Open an apparent file
 >>> fgif = file('xx.gif','w')
 FileHook('xx.gif', 'w')

Write data and close
 >>> fgif.write('<dummy gif data>')
 >>> fgif.close()

Check what we caught
 >>> gifdata.data
 ['<dummy gif data>']

Multiple writes should be expected, and will obviously produce a list
so the net gif string would be ''.join(gifdata.data).

But I would say the above is NOT recommended other than for temporarily
enabling testing of other aspects of a program.

I didn't try it with your first program. It might well[1] not work, if the file is
opened by tk infrastructure doing its own i/o and ignoring python's file feature.

[1] Read won't: it opens its own file. 
</ignorable>

<alternative>
Maybe consider using PIL instead. its image save method will accept a file
object if it supports write, seek, and tell.

I modified your program to use PIL rather than write an equivalent. This makes a
full color .gif. Note little changes here and there ;-)

I hacked together a Writeable class that seems to satisfy PIL's save method's
requirement.

====< stuyvesant.py >============================================================
#from Tkinter import *
#import Tkinter
from PIL import Image

class Writeable(object):
    from array import array
    def __init__(self):
        self.data = self.array('B')
        self.pos = 0
    def seek(self, pos):
        nb = len(self.data)
        if pos>nb:
            self.data[nb:pos] = self.array('B','\0'*(pos-nb))
        self.pos = pos
    def tell(self): return self.pos
    def write(self, data):
        nb = len(data)
        self.data[self.pos:self.pos+nb] = self.array('B',data)
        self.pos +=nb
    def __len__(self): return len(self.data)
    def tostring(self): return self.data.tostring()

class MyGif:
    def __init__(self, color="", width=1, height=1):        
        self.image = Image.new('RGB', (width, height)) # PhotoImage(width=width, height=height)
        self.gif = None
        if color:
            for row in range(height):
                for col in range(width):
                    if row % 2 == 0:
                        if col % 2 == 0:
                            self.colorXY(color, col, row)
                
    def colorXY(self, color, col, row):
        #self.image.put(color, (col, row))
        self.image.putpixel((col, row), color)

    def __len__(self):
        return len(self.getgif())
        #return 342  # XXX  compute .gif size of self.image
                    # HOW TO DO THIS?

    def getgif(self, recompute=0):
        if self.gif is None or recompute:
            w = Writeable()
            self.image.save(w,'GIF') # write(w) # will this be intercepted??
            self.gif = w.tostring()
        return self.gif 

#Tkinter.Tk()
image = MyGif(width=100, height=10, color=0xFF0000) # (little endian RGB) # "blue")

# I want to avoid this file creation:
#image.image.write('test.gif')
#fp = open('test.gif')
#fdata = fp.read()
fdata = image.getgif(1)
wwwgif = '''\
Content-type: image/gif
Content-length: %s

%s''' % (len(fdata), fdata)

file('test.gif','wb').write(fdata) # this writes the gif string in binary to a file.
# To write it with your Content-type format, I doubt if you can do it with an ordinary print,
# beacuse it may alter the data. You will most likely have to change the output mode
# to binary and use the write method to get the gif data correctly to the server.
#
# You can run this if you have PIL installed, and then look at test.gif with browser or
# graphics program to verify. I haven't run the server part.
#
=================================================================================

You can also generate palette-based or black and white gifs. I just took the easy route.
</alternative>

BTW, this is not the fastest way to use PIL to put pixels. It just was close to the way
your program was doing it. Note also black backgound, and no 'blue' lookup, etc.

Regards,
Bengt Richter



More information about the Python-list mailing list