Title: Load and display an image at runtime with Python and PIL
This is mostly straightforward, but there are a few little gotchas that require a little extra thought. This post first explains how to let the user select an image file and how to load that file. It then explains a few things that you should do to make the image display nicely.
Selecting Files
See the post Build standard menus in Python and tkinter for information about how the program builds its menus.
When the user selects the File menu's Open Image command, the following code executes.
def mnu_open_image(self):
self.pil_image = self.get_image_file('Load Image')
self.show_image()
This code first calls get_image_file to let the user select and open an image file. It saves the loaded image in self.pil_image.
Next the code calls show_image to display the result. The program does this in two steps to make it easier for some later programs that will process the image before displaying it.
The following code shows the get_image_file method.
def get_image_file(self, title):
'''
Let the user pick an image file.
Then load and return it in an RGB PIL Image.
'''
filetypes = (
('Image Files', '*.png;*.jpg;*.bmp;*.gif;*.tif'),
('All files', '*.*')
)
filename = fd.askopenfilename(title=title, filetypes=filetypes)
if not filename: return None
try:
return Image.open(filename)
except Exception as e:
messagebox.showinfo('Open Error', f'Error loading image file.\n{e}')
return None
This code first creates a list of file types and their extensions. To include multiple extensions for a file type, separate the extensions with semi-colons. (You can also separate them with spaces, but the dialog displays them separated with semi-colons so you may as well follow suit.)
If you want to allow older file extensions like .tiff and .jpeg, you must include them explicitly. The file selection dialog does not consider .jpeg the same as .jpg.
Next, the code calls askopenfilename to let the user select a file. If the user cancels the dialog, the code returns None.
The method then tries to load the file. There are many reasons why this might fail (for example, if the user selects a text file), so it performs this inside a try-except block.
The program uses Image.open to load the file and returns the result.
If there's an error, the program displays a message and returns None.
The following code shows the show_image method.
def show_image(self):
'''Display the image.'''
if self.pil_image == None: return
# Convert into a PhotoImage.
self.photo_image = ImageTk.PhotoImage(self.pil_image)
# Display it.
self.canvas.delete(tk.ALL)
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo_image)
wid = self.photo_image.width()
hgt = self.photo_image.height()
self.canvas.config(width=wid, height=hgt)
This method checks self.pil_image and returns if no image file is loaded.
If a file is loaded, it converts the image into a PhotoImage. It then deletes any existing objects from the program's Canvas widget and uses create_image to display the PhotoImage.
The method then gets the image's size and configures the canvas to have that size.
That should be the end of the story, but we need to address an annoying little detail provided by the Canvas widget.
Pixel Perfection
If you load an image into a Canvas widget using most of the default settings, the image doesn't quite fit. By default, the canvas displays a highlight border. You usually won't notice it, but it messes up the image's alignment, at least if you need pixel perfect control.
The widget expands slightly to allow room for the highlight border, so its size isn't quite what you would like if you're trying to fit to an image. Even stranger, the widget keeps its origin its extreme upper left corner under the border. That means if you position the image at (0, 0), it lies partly below the border. That also means it doesn't fill the widget so there's some empty space on the right and bottom.
To see this, take a look at the picture below. Here I've given the window a light blue background and the canvas has a white background so it's a bit easier to see the problem. The picture on the left shows what you get if you keep the highlight border. The picture on the right shows the desired result.
It's fairly easy to see how image's lower right corner doesn't fill the canvas in the left picture. It's a bit harder to see the problem with the upper left corner, but you can see (again on the left) that the top of the little blob lies below the highlight border.
So what's the solution? It's actually pretty simple once you figure it out: just set the Canvas widget's border and highlight border thicknesses to zero as shown in the red part of the following code.
see_problem = False
self.window = tk.Tk()
# Use the following to see the highlight border more easily.
if see_problem: self.window.configure(bg='lightblue')
self.window.title('pil_load_image')
self.window.protocol('WM_DELETE_WINDOW', self.kill_callback)
self.window.geometry('300x300')
# Build the menu.
self.build_menus()
# Create the Canvas.
if see_problem:
# See the highlight border more easily.
self.canvas = tk.Canvas(self.window, bg='white', width=10, height=10)
else:
# Set highlightthickness to prevent the Canvas from having a border.
bg = self.window.cget('bg')
self.canvas = tk.Canvas(self.window, bg=bg, width=10, height=10,
borderwidth=0, highlightthickness=0)
self.canvas.pack(side=tk.TOP, fill=tk.NONE, expand=False, anchor=tk.NW,
padx=10, pady=10)
If you want to see the problem, set the see_problem variable to True.
Conclusion
If you want an image to fit a Canvas widget perfectly, ditch the border and highlight border. That's particularly important when you want to work directly on the image's pixels and exact positioning is important.
Download the example to see additional details.
For more information image processing in Python, see my Manning liveProject Algorithm Projects with Python: Image Processing. It explain how to do things like:
|
• Rotation | • Scaling | • Stretching |
• Brightness enhancement | • Contrast enhancement | • Cropping |
• Remapping colors | • Image sharpening | • Embossing |
• Color enhancement | • Sharpening | • Gray scale |
• Black and white | • Sepia tone | • Other color scales |
|