I l@ve RuBoard Previous Section Next Section

7.9 Images

In Tkinter, graphical images are displayed by creating independent PhotoImage or BitmapImage objects, and then attaching those image objects to other widgets via image attribute settings. Buttons, labels, canvases, text, and menus can all display images by associating prebuilt image objects this way. To illustrate, Example 7-36 throws a picture up on a button.

Example 7-36. PP2E\Gui\Tour\imgButton.py
gifdir = "../gifs/"
from Tkinter import *
win = Tk()
igm = PhotoImage(file=gifdir+"ora-pp.gif")
Button(win, image=igm).pack()
win.mainloop()

I could try to come up with a simpler example, but it would be tough -- all this script does is make a Tkinter PhotoImage object for a GIF file stored in another directory, and associate it with a Button widget's image option. The result is captured in Figure 7-36.

Figure 7-36. imgButton in action
figs/ppy2_0736.gif

PhotoImage and its cousin, BitmapImage, essentially load graphics files, and allow those graphics to be attached to other kinds of widgets. To open a picture file, pass its name to the file attribute of these image objects. Canvas widgets -- general drawing surfaces discussed in more detail later on this tour -- can display pictures too; Example 7-37 renders Figure 7-37.

Example 7-37. PP2E\Gui\Tour\imgCanvas.py
gifdir = "../gifs/"
from Tkinter import *
win = Tk()
img = PhotoImage(file=gifdir+"ora-lp.gif")
can = Canvas(win)
can.pack(fill=BOTH)
can.create_image(2, 2, image=img, anchor=NW)           # x, y coordinates
win.mainloop()
Figure 7-37. An image on canvas
figs/ppy2_0737.gif

Buttons are automatically sized to fit an associated photo, but canvases are not (because you can add objects to a canvas, as we'll see in Chapter 8). To make a canvas fit the picture, size it according to the width and height methods of image objects as in Example 7-38. This version will make the canvas smaller or larger than its default size as needed, lets you pass in a photo file's name on the command line, and can be used as a simple image viewer utility. The visual effect of this script is captured in Figure 7-38.

Example 7-38. PP2E\Gui\Tour\imgCanvas2.py
gifdir = "../gifs/"
from sys import argv
from Tkinter import *
filename = (len(argv) > 1 and argv[1]) or 'ora-lp.gif'    # name on cmdline?
win = Tk()
img = PhotoImage(file=gifdir+filename)
can = Canvas(win)
can.pack(fill=BOTH)
can.config(width=img.width(), height=img.height())        # size to img size
can.create_image(2, 2, image=img, anchor=NW)
win.mainloop()
Figure 7-38. Sizing the canvas to match the photo
figs/ppy2_0738.gif

And that's all there is to it. In Chapter 8, well see images show up in a Menu, other Canvas examples, and the image-friendly Text widget. In later chapters, we'll find them in an image slideshow (PyView), in a paint program (PyDraw), on clocks (PyClock), and so on. It's easy to add graphics to GUIs in Python/Tkinter.

Once you start using photos in earnest, though, you're likely to run into two tricky bits which I want to warn you about here:

Supported file types

At present, the PhotoImage widget only supports GIF, PPM, and PGM graphic file formats, and BitmapImage supports X Windows-style .xbm bitmap files. This may be expanded in future releases, and you can convert photos in other formats to these supported formats, of course; but I'll wait to address this issue in more detail in Other Image File Formats: PIL later in this section.

Hold on to your photos

Unlike all other Tkinter widgets, an image is utterly lost if the corresponding Python image object is garbage-collected. That means that you must retain an explicit reference to image objects for as long as your program needs them (e.g., assign them to a long-lived variable name or data structure component). Python does not automatically keep a reference to the image, even if it is linked to other GUI components for display; moreover, image destructor methods erase the image from memory. We saw earlier that Tkinter variables can behave oddly when reclaimed too, but the effect is much worse and more likely to happen with images. This may change in future Python releases (though there are good reasons for not retaining big image files in memory indefinitely); for now, though, images are a "use it or lose it" widget.

7.9.1 Fun with Buttons and Pictures

I tried to come up with an image demo for this section that was both fun and useful. I settled for the fun part. Example 7-39 displays a button that changes its image at random each time it is pressed.

Example 7-39. PP2E\Gui\Tour\buttonpics-func.py
from Tkinter import *                # get base widget set
from glob import glob                # file name expansion list
import demoCheck                     # attach checkbutton demo to me
import random                        # pick a picture at random
gifdir = '../gifs/'                  # where to look for gif files

def draw():
    name, photo = random.choice(images)
    lbl.config(text=name)
    pix.config(image=photo)

root=Tk()
lbl = Label(root,  text="none", bg='blue', fg='red')
pix = Button(root, text="Press me", command=draw, bg='white')
lbl.pack(fill=BOTH)
pix.pack(pady=10)
demoCheck.Demo(root, relief=SUNKEN, bd=2).pack(fill=BOTH)

files = glob(gifdir + "*.gif")                              # gifs for now
images = map(lambda x: (x, PhotoImage(file=x)), files)      # load and hold
print files
root.mainloop()

This code uses a handful of built-in tools from the Python library:

  • The Python glob module we met earlier in the book gives a list of all files ending in .gif in a directory; in other words, all GIF files stored there.

  • The Python random module is used to select a random GIF from files in the directory: random.choice picks and returns an item from a list at random.

  • To change the image displayed (and the GIF file's name in a label at the top of the window), the script simply calls the widget config method with new option settings; changing on the fly like this changes the widget's display.

Just for fun, this script also attaches an instance of the demoCheck checkbox demo bar, which in turn attaches an instance of the Quitter button we wrote earlier. This is an artificial example, of course, but again demonstrates the power of component class attachment at work.

Notice how this script builds and holds onto all images in its images list. The map here applies a PhotoImage constructor call to every .gif file in the photo directory, producing a list of (file,image) tuples that is saved in a global variable. Remember, this guarantees that image objects won't be garbage-collected as long as the program is running. Figure 7-39 shows this script in action on Windows.

Figure 7-39. buttonpics in action
figs/ppy2_0739.gif

Although it may not be obvious in this grayscale book, the name of the GIF file being displayed is shown in red text in the blue label at the top of this window. This program's window grows and shrinks automatically when larger and smaller GIF files are displayed; Figure 7-40 shows it randomly picking a taller photo globbed from the image directory.

Figure 7-40. buttonpics showing a taller photo
figs/ppy2_0740.gif

And finally, Figure 7-41 captures this script's GUI displaying one of the wider GIFs, selected completely at random from the photo file directory.[3]

[3] This particular image appeared as a banner ad on developer-related web sites like slashdot.com when the book Learning Python was first published. It generated enough of a backlash from Perl zealots that O'Reilly eventually pulled the ad altogether. Which is why, of course, it appears in this book.

Figure 7-41. buttonpics gets political
figs/ppy2_0741.gif

While we're playing, let's recode this script as a class in case we ever want to attach or customize it later (it could happen). It's mostly a matter of indenting and adding self before global variable names, as shown in Example 7-40.

Example 7-40. PP2E\Gui\Tour\buttonpics.py
from Tkinter import *                # get base widget set
from glob import glob                # file name expansion list
import demoCheck                     # attach checkbox example to me
import random                        # pick a picture at random
gifdir = '../gifs/'                  # default dir to load gif files

class ButtonPicsDemo(Frame):
    def __init__(self, gifdir=gifdir, parent=None):
        Frame.__init__(self, parent)
        self.pack()
        self.lbl = Label(self,  text="none", bg='blue', fg='red')
        self.pix = Button(self, text="Press me", command=self.draw, bg='white')
        self.lbl.pack(fill=BOTH)
        self.pix.pack(pady=10)
        demoCheck.Demo(self, relief=SUNKEN, bd=2).pack(fill=BOTH)
        files = glob(gifdir + "*.gif")
        self.images = map(lambda x: (x, PhotoImage(file=x)), files)
        print files
        
    def draw(self):
        name, photo = random.choice(self.images)
        self.lbl.config(text=name)
        self.pix.config(image=photo)

if __name__ == '__main__': ButtonPicsDemo().mainloop()

This version works the same as the original, but can now be attached to any other GUI where you would like to include such an unreasonably silly button.

Other Image File Formats: PIL

As mentioned, Python Tkinter scripts show images by associating independently created image objects with real widget objects. At this writing, Tkinter GUIs can display photo image files in GIF, PPM, and PGM formats by creating a PhotoImage object, as well as X11-style bitmap files (usually suffixed with a .xbm extension) by creating a BitmapImage object.

This set of supported file formats is limited by the underlying Tk library, not Tkinter itself, and may expand in the future. But if you want to display files in other formats today (e.g., JPEG and BMP), you can either convert your files to one of the supported formats with an image-processing program, or install the PIL Python extension package mentioned at the start of Chapter 6.

PIL currently supports nearly 30 graphic file formats (including JPEG and BMP). To make use of its tools, you must first fetch and install the PIL package (see http://www.pythonware.com). Then, simply use special PhotoImage and BitmapImage objects imported from the PIL ImageTk module to open files in other graphic formats. These are compatible replacements for the standard Tkinter classes of the same name, and may be used anywhere Tkinter expects a PhotoImage or BitmapImage object (i.e., in label, button, canvas, text, and menu object configurations). That is, replace standard Tkinter code like this:

from Tkinter import *
imgobj = PhotoImage(file=imgdir+"spam.gif")
Button(image=imgobj).pack()

With code of this form:

from Tkinter import *
import ImageTk
imgobj = ImageTk.PhotoImage(file=imgdir+"spam.jpg")
Button(image=imgobj).pack()

Or the more verbose equivalent:

from Tkinter import *
import Image, ImageTk
imagefile = Image.open(imgdir+"spam.jpeg")
imageobj  = ImageTk.PhotoImage(imagefile)
Label(image=imageobj).pack()

PIL installation details vary per platform; on my Windows laptop, it was just a matter of downloading, unzipping, and adding PIL directories to the front of PYTHONPATH. There is much more to PIL than we have time to cover here; for instance, it also provides image conversion, resizing, and transformation tools, some of which can be run as command-line programs that have nothing to do with GUIs directly. See http://www.pythonware.com for more information, as well as online PIL and Tkinter documentation sets.

    I l@ve RuBoard Previous Section Next Section