9.10. Viewing and Processing Images with PIL
As mentioned earlier, 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 an .xbm extension) by creating a BitmapImage object.
This set of supported file formats is limited by the underlying Tk library, not by Tkinter itself, and may expand in the future. But if you want to display files in other formats today (e.g., the popular JPEG format), 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 8.
PIL, the Python Imaging Library, is an open source system that supports nearly 30 graphics file formats (including GIF, JPEG, TIFF, and BMP). In addition to allowing your scripts to display a much wider variety of image types than standard Tkinter, PIL also provides tools for image processing, including geometric transforms, thumbnail creation, format conversions, and much more.
9.10.1. PIL Basics
To use its tools, you must first fetch and install the PIL package: see http://www.pythonware.com (or search for "PIL" on Google). 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 they 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 such as this:
from Tkinter import * imgobj = PhotoImage(file=imgdir + "spam.gif") Button(image=imgobj).pack( )
with code of this form:
from Tkinter import * import ImageTk photoimg = ImageTk.PhotoImage(file=imgdir + "spam.jpg") Button(image=photoimg).pack( )
or with the more verbose equivalent, which comes in handy if you will perform image processing in addition to image display:
from Tkinter import * import Image, ImageTk imageobj = Image.open(imgdir + "spam.jpeg") photoimg = ImageTk.PhotoImage(imageobj) Button(image=photoimg).pack( )
In fact, to use PIL for image display, all you really need to do is install it and add a single from statement to your code to get its replacement PhotoImage object, after loading the original from Tkinter. The rest of your code remains unchanged but will be able to display JPEG and other image types:
from Tkinter import * from ImageTk import PhotoImage # <== add this line imgobj = PhotoImage(file=imgdir + "spam.jpg") Button(image=imgobj).pack( )
PIL installation details vary per platform; on Windows, it is just a matter of downloading and running a self-installer. PIL code winds up in the Python install directory's Lib\site packages; because this is automatically added to the module import search path, no path configuration is required to use PIL. Simply run the installer and import PIL modules. On other platforms, you might untar or unZIP a fetched source code archive and add PIL directories to the front of your PYTHONPATH setting; see the PIL system's web site for more details.
There is much more to PIL than we have space 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. Especially for Tkinter-based programs that display or process images, PIL will likely become a standard component in your software tool set.
See http://www.pythonware.com for more information, as well as online PIL and Tkinter documentation sets. To help get you started, though, we'll close out this chapter with a handful of real scripts that use PIL for image display and processing.
9.10.2. Displaying Other Image Types with PIL
In our earlier image examples, we attached widgets to buttons and canvases, but the standard Tkinter toolkit allows images to be added to a variety of widget types, including simple labels, text, and menu entries. Example 9-41, for instance, uses unadorned Tkinter to display a single image by attaching it to a label, in the main application window. The example assumes that images are stored in an images subdirectory, and allows the image filename to be passed in as a command-line argument (it defaults to spam.gif if no argument is passed). It also prints the image's height and width in pixels to the standard output stream, just to give extra information.
Example 9-41. PP3E\Gui\PIL\viewer-tk.py
Figure 9-42 captures this script's display on Windows XP, showing the default GIF image file. Run this from the system console with a filename as a command-line argument to view other files (e.g., python viewer_tk.py filename.gif).
Figure 9-42. Tkinter GIF display
Example 9-41 works but only for image types supported by the base Tkinter toolkit. To display other image formats such as JPEG, we need to install PIL and use its replacement PhotoImage object. In terms of code, it's simply a matter of adding one import statement, as illustrated in Example 9-42.
Example 9-42. PP3E\GuiPIL\viewer-pil.py
With PIL, our script is now able to display many image types, including the default JPEG image defined in the script and captured in Figure 9-43.
Figure 9-43. Tkinter+PIL JPEG display
126.96.36.199. Displaying all images in a directory
While we're at it, it's not much extra work to allow viewing all images in a directory, using some of the directory path tools we met in the first part of this book. Example 9-43, for instance, simply opens a new Toplevel pop-up window for each image in a directory (given as a command-line argument, or a default), taking care to skip nonimage files by catching exceptions.
Example 9-43. PP3E\Gui\PIL\viewer-dir.py
Run this code on your own to see the windows it generates. If you do, you'll get one main window with a Quit button, plus as many pop-up image view windows as there are images in the directory. This is convenient for a quick look, but not exactly the epitome of user friendliness for large directoriesthose created by your digital camera, for instance. To do better, let's move on to the next section.
9.10.3. Creating Image Thumbnails with PIL
As mentioned, PIL does more than display images in a GUI; it also comes with tools for resizing, converting, and more. One of the many useful tools it provides is the ability to generate small, "thumbnail" images from originals. Such thumbnails may be displayed in a web page or selection GUI to allow the user to open full-size images on demand.
Example 9-44 is a concrete implementation of this ideait generates thumbnail images using PIL and displays them on buttons which open the corresponding original image when clicked. The net effect is much like the file explorer GUIs that are now standard on modern operating systems, but by coding this in Python, we're able to control its behavior and to reuse and customize its code in our own applications. As usual, these are some of the primary benefits inherent in open source software in general.
Example 9-44. PP3E\Gui\PIL\viewer_thumbs.py
Most of the PIL-specific code in this example is in the makeThumbs function. It opens, creates, and saves the thumbnail image, unless one has already been saved (i.e., cached) to a local file. As coded, thumbnail images are saved in the same image format as the original full-size photo.
We also use the PIL ANTIALIAS filterthe best quality for down-sampling (shrinking), and likely the default in the future; this does a better job on low-resolution GIFs. Thumbnail generation is essentially just an in-place resize that preserves the original aspect ratio. We'll defer to PIL documentation for more details on that package's API.
But notice how this code must pass in the imgfile to the generated callback handler with a default argument; as we've learned, because imgfile is a loop variable, all callbacks will have its final loop iteration value if its current value is not saved this way (all buttons would open the same image!). Also notice how we keep a list of references to the photo image objects; as we've also seen, photos are erased when their object is garbage collected, even if they are currently being displayed. To avoid this, we simply generate references in a long-lived list.
Figure 9-44 shows the main thumbnail selection window generated by Example 9-44 when you're viewing the default images subdirectory in the examples source tree. As in the previous examples, you can pass in an optional directory name to run the viewer on a directory of your own (for instance, one copied from your digital camera). Clicking on any thumbnail button in the main window opens a corresponding image in an independent pop-up window; Figure 9-45 captures one of these.
Figure 9-44. Simple thumbnail selection GUI
Figure 9-45. Thumbnail viewer pop-up image window
188.8.131.52. Performance: saving thumbnail files
Before we move on, three variations on the thumbnail viewer are worth considering. The first underscores performance concepts. As is, the viewer saves the generated thumbnail image in a file, so it can be loaded quickly the next time the script is run. This isn't strictly requiredExample 9-45, for instance, customizes the thumbnail generation function to generate the thumbnail images in memory, but never save them.
There is no noticeable speed difference for small image collections. If you run these alternatives on larger image collections, though, you'll notice that the original version in Example 9-44 gains a big performance advantage by saving and loading the thumbnails to files; on some tests with many large image files on my machine, the original version usually opens the GUI in roughly 1 second, compared to as much as 5 to 15 seconds for Example 9-45. For thumbnails, loading from files is quicker than recalculation.
Example 9-45. PP3E\Gui\PIL\viewer-thumbs-nosave.py
184.108.40.206. Layout: gridding and fixed-size widgets
The next variations on our viewer are purely cosmetic, but they illustrate Tkinter layout concepts. If you look at Figure 9-44 long enough, you'll notice that its layout of thumbnails is not as uniform as it could be. For larger collections, it could become difficult to locate and open specific images. With just a little extra work, we can achieve a more uniform layout by either laying out the thumbnails in a grid, or using uniform fixed-size buttons. Example 9-46 positions buttons in a row/column grid by using the Tkinter grid geometry managera topic we will explore in more detail in the next chapter; you should consider some of this code a preview.
Example 9-46. PP3E\Gui\PIL\viewer-thumbs-grid.py
Figure 9-46 displays the effect of gridding; our buttons line up in rows and columns in a more uniform fashion.
Figure 9-46. Gridded thumbnail selection GUI
We can achieve a layout that is perhaps even more uniform than gridding, by giving each thumbnail button a fixed size. Example 9-47 does the trick. It sets the height and width of each button to match the maximum dimension of the thumbnail icon.
Example 9-47. PP3E\Gui\PIL\viewer-thumbs-fixed.py
Figure 9-47 shows the results of applying a fixed size to our buttons; all are the same size now, using a size taken from the images themselves. Naturally, other layout schemes are possible as well; experiment with some of the configuration options in this code on your own to see their effect on the display.
Figure 9-47. Fixed-size thumbnail selection GUI
220.127.116.11. Scrolling and canvases
The thumbnail viewer scripts presented in this section work well for reasonably sized image directories, and you can use smaller thumbnail size settings for larger image collections. Perhaps the biggest limitation of these programs, though, is that the thumbnail windows they create will become too large to handle (or display at all) if the image directory contains very many files. A directory copied from your camera with more than 100 images, for example, might produce a window too large to fit on your computer's screen.
To do better, we could arrange the thumbnails on a widget that supports scrolling. The open source Pmw package includes a handy scrolled frame that may help. Moreover, the standard Tkinter Canvas widget gives us more control over image displays and supports horizontal and vertical scrolling.
In fact, one final extension to our scripts in the source code directory, viewer_thumbs_scrolled.py, does just thatit displays thumbnails in a scrolled canvas and so handles large collections much better. We'll study that extension in conjunction with canvases in the next chapter. And in Chapter 12, we'll apply this technique to a more full-featured image viewing program called PyPhoto, whose main window is captured in Figure 9-48. To learn how these programs do their job, though, we need to move on to the next chapter, and the second half of our widget tour.
Figure 9-48. Scrollable thumbnail selection GUI