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.
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 Libsite 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.
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. PP3EGuiPILviewer-tk.py
####################################################### # show one image with standard Tkinter photo object # as is this handles GIF files, but not JPEG images # image filename listed in command line, or default # use a Canvas instead of Label for scrolling, etc. ####################################################### import os, sys from Tkinter import * # use standard Tkinter photo object # GIF works, but JPEG requires PIL imgdir = 'images' imgfile = 'newmarket-uk-2.gif' if len(sys.argv) > 1: # cmdline argument given? imgfile = sys.argv[1] imgpath = os.path.join(imgdir, imgfile) win = Tk( ) win.title(imgfile) imgobj = PhotoImage(file=imgpath) # display photo on a Label Label(win, image=imgobj).pack( ) print imgobj.width(), imgobj.height( ) # show size in pixels before destroyed win.mainloop( )
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).
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. PP3EGuiPILviewer-pil.py
####################################################### # show one image with PIL photo replacement object # install PIL first: placed in Libsite-packages ####################################################### import os, sys from Tkinter import * from ImageTk import PhotoImage # <== use PIL replacement class # rest of code unchanged imgdir = 'images' imgfile = 'newmarket-uk-1.jpg' if len(sys.argv) > 1: imgfile = sys.argv[1] imgpath = os.path.join(imgdir, imgfile) win = Tk( ) win.title(imgfile) imgobj = PhotoImage(file=imgpath) # now JPEGs work! Label(win, image=imgobj).pack( ) win.mainloop( ) print imgobj.width(), imgobj.height( ) # show size in pixels on exit
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.
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. PP3EGuiPILviewer-dir.py
####################################################### # display all images in a directory in pop-up windows # GIFs work, but JPEGs will be skipped without PIL ####################################################### import os, sys from Tkinter import * from ImageTk import PhotoImage # <== required for JPEGs and others imgdir = 'images' if len(sys.argv) > 1: imgdir = sys.argv[1] imgfiles = os.listdir(imgdir) # does not include directory prefix main = Tk( ) main.title('Viewer') quit = Button(main, text='Quit all', command=main.quit, font=('courier', 25)) quit.pack( ) savephotos = [] for imgfile in imgfiles: imgpath = os.path.join(imgdir, imgfile) win = Toplevel( ) win.title(imgfile) try: imgobj = PhotoImage(file=imgpath) Label(win, image=imgobj).pack( ) print imgpath, imgobj.width(), imgobj.height( ) # size in pixels savephotos.append(imgobj) # keep a reference except: errmsg = 'skipping %s %s' % (imgfile, sys.exc_info( )[1]) Label(win, text=errmsg).pack( ) main.mainloop( )
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 directories—those created by your digital camera, for instance. To do better, let’s move on to the next section.
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 idea—it 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. PP3EGuiPILviewer_thumbs.py
####################################################### # display all images in a directory as thumbnail image # buttons that display the full image when clicked; # requires PIL for JPEGs and thumbnail img creation; # to do: add scrolling if too many thumbs for window! ####################################################### import os, sys, math from Tkinter import * import Image # <== required for thumbs from ImageTk import PhotoImage # <== required for JPEG display def makeThumbs(imgdir, size=(100, 100), subdir='thumbs'): """ get thumbnail images for all images in a directory; for each image, create and save a new thumb, or load and return an existing thumb; makes thumb dir if needed; returns list of (image filename, thumb image object); the caller can also run listdir on thumb dir to load; on bad file types we may get IOError, or other: overflow """ thumbdir = os.path.join(imgdir, subdir) if not os.path.exists(thumbdir): os.mkdir(thumbdir) thumbs = [] for imgfile in os.listdir(imgdir): thumbpath = os.path.join(thumbdir, imgfile) if os.path.exists(thumbpath): thumbobj = Image.open(thumbpath) # use already created thumbs.append((imgfile, thumbobj)) else: print 'making', thumbpath imgpath = os.path.join(imgdir, imgfile) try: imgobj = Image.open(imgpath) # make new thumb imgobj.thumbnail(size, Image.ANTIALIAS) # best downsize filter imgobj.save(thumbpath) # type via ext or passed thumbs.append((imgfile, imgobj)) except: # not always IOError print "Skipping: ", imgpath return thumbs class ViewOne(Toplevel): """ open a single image in a pop-up window when created; photoimage obj must be saved: erased if reclaimed; """ def _ _init_ _(self, imgdir, imgfile): Toplevel._ _init_ _(self) self.title(imgfile) imgpath = os.path.join(imgdir, imgfile) imgobj = PhotoImage(file=imgpath) Label(self, image=imgobj).pack( ) print imgpath, imgobj.width(), imgobj.height( ) # size in pixels self.savephoto = imgobj # keep reference on me def viewer(imgdir, kind=Toplevel, cols=None): """ make thumb links window for an image directory: one thumb button per image; use kind=Tk to show in main app window, or Frame container (pack); imgfile differs per loop: must save with a default; photoimage objs must be saved: erased if reclaimed; """ win = kind( ) win.title('Viewer: ' + imgdir) thumbs = makeThumbs(imgdir) if not cols: cols = int(math.ceil(math.sqrt(len(thumbs)))) # fixed or N x N savephotos = [] while thumbs: thumbsrow, thumbs = thumbs[:cols], thumbs[cols:] row = Frame(win) row.pack(fill=BOTH) for (imgfile, imgobj) in thumbsrow: photo = PhotoImage(imgobj) link = Button(row, image=photo) handler = lambda savefile=imgfile: ViewOne(imgdir, savefile) link.config(command=handler) link.pack(side=LEFT, expand=YES) savephotos.append(photo) Button(win, text='Quit', command=win.quit).pack(fill=X) return win, savephotos if _ _name_ _ == '_ _main_ _': imgdir = (len(sys.argv) > 1 and sys.argv[1]) or 'images' main, save = viewer(imgdir, kind=Tk) main.mainloop( )
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
filter—the 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.
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 required—Example 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. PP3EGuiPILviewer-thumbs-nosave.py
########################################################## # same, but make thumb images in memory without saving # to or loading from files -- this seems just as fast # for small directories, but saving to files makes # startup much quicker for large image collections; # saving may also be needed in some apps (e.g., web pages) ########################################################## import os, sys import Image from Tkinter import Tk import viewer_thumbs def makeThumbs(imgdir, size=(100, 100), subdir='thumbs'): """ create thumbs in memory but don't cache to files """ thumbs = [] for imgfile in os.listdir(imgdir): imgpath = os.path.join(imgdir, imgfile) try: imgobj = Image.open(imgpath) # make new thumb imgobj.thumbnail(size) thumbs.append((imgfile, imgobj)) except: print "Skipping: ", imgpath return thumbs if _ _name_ _ == '_ _main_ _': imgdir = (len(sys.argv) > 1 and sys.argv[1]) or 'images' viewer_thumbs.makeThumbs = makeThumbs main, save = viewer_thumbs.viewer(imgdir, kind=Tk) main.mainloop( )
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 manager—a topic we will explore in more detail in the next chapter; you should consider some of this code a preview.
Example 9-46. PP3EGuiPILviewer-thumbs-grid.py
####################################################### # same as viewer_thumbs, but uses the grid geometry # manager to try to achieve a more uniform layout; # can generally achieve the same with frames and pack # if buttons are all fixed and uniform in size; ####################################################### import sys, math from Tkinter import * from ImageTk import PhotoImage from viewer_thumbs import makeThumbs, ViewOne def viewer(imgdir, kind=Toplevel, cols=None): """ custom version that uses gridding """ win = kind( ) win.title('Viewer: ' + imgdir) thumbs = makeThumbs(imgdir) if not cols: cols = int(math.ceil(math.sqrt(len(thumbs)))) # fixed or N x N rownum = 0 savephotos = [] while thumbs: thumbsrow, thumbs = thumbs[:cols], thumbs[cols:] colnum = 0 for (imgfile, imgobj) in thumbsrow: photo = PhotoImage(imgobj) link = Button(win, image=photo) handler = lambda savefile=imgfile: ViewOne(imgdir, savefile) link.config(command=handler) link.grid(row=rownum, column=colnum) savephotos.append(photo) colnum += 1 rownum += 1 Button(win, text='Quit', command=win.quit).grid(columnspan=cols, stick=EW) return win, savephotos if _ _name_ _ == '_ _main_ _': imgdir = (len(sys.argv) > 1 and sys.argv[1]) or 'images' main, save = viewer(imgdir, kind=Tk) main.mainloop( )
Figure 9-46 displays the effect of gridding; our buttons line up in rows and columns in a more uniform fashion.
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. PP3EGuiPILviewer-thumbs-fixed.py
####################################################### # use fixed size for thumbnails, so align regularly; # size taken from image object, assume all same max; # this is essentially what file selection GUIs do; ####################################################### import sys, math from Tkinter import * from ImageTk import PhotoImage from viewer_thumbs import makeThumbs, ViewOne def viewer(imgdir, kind=Toplevel, cols=None): """ custom version that lays out with fixed-size buttons """ win = kind( ) win.title('Viewer: ' + imgdir) thumbs = makeThumbs(imgdir) if not cols: cols = int(math.ceil(math.sqrt(len(thumbs)))) # fixed or N x N savephotos = [] while thumbs: thumbsrow, thumbs = thumbs[:cols], thumbs[cols:] row = Frame(win) row.pack(fill=BOTH) for (imgfile, imgobj) in thumbsrow: size = max(imgobj.size) # width, height photo = PhotoImage(imgobj) link = Button(row, image=photo) handler = lambda savefile=imgfile: ViewOne(imgdir, savefile) link.config(command=handler, width=size, height=size) link.pack(side=LEFT, expand=YES) savephotos.append(photo) Button(win, text='Quit', command=win.quit, bg='beige').pack(fill=X) return win, savephotos if _ _name_ _ == '_ _main_ _': imgdir = (len(sys.argv) > 1 and sys.argv[1]) or 'images' main, save = viewer(imgdir, kind=Tk) main.mainloop( )
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.
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 that—it 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.
3.16.137.38