How to Display Images in Python Tkinter?

A bakery owner wanted a register-side window that cycles through menu photos. That’s a Tkinter image job: load the file, wrap it in a PhotoImage, show it on a Label.

This is the finished viewer, running for real:

Finished Tkinter image viewer showing a butter croissant photo, the caption Butter Croissant $4.25 (2 of 3) and Previous and Next buttons
The finished viewer after one click on Next.

In this tutorial, I’ll build that Tkinter image viewer step by step on Python 3.12.5 with Tk 8.6 and Pillow 12.3.0, then fix the three bugs I hit.

Before You Start: install Pillow for Tkinter images

Tkinter ships with Python, but you’ll need Pillow for JPG files and resizing:

pip install pillow

You can use any images you have. I generated these with Pillow and kept them next to the script:

FileSize (px)Menu item
logo.png120 x 120Shop logo
muffin.jpg1600 x 1067Blueberry Muffin, $3.75
croissant.jpg1600 x 1067Butter Croissant, $4.25
pie.jpg1600 x 1067Apple Pie Slice, $5.50

Step 1: Show a PNG image with tk.PhotoImage

Tkinter’s own PhotoImage reads PNG and GIF, so the logo needs no extra package:

import tkinter as tk

root = tk.Tk()
root.title("Maple Street Bakery")
root.geometry("560x230")

logo = tk.PhotoImage(file="logo.png")   # PNG/GIF, no Pillow needed
logo_label = tk.Label(root, image=logo)
logo_label.pack(pady=(24, 8))
tk.Label(root, text="Maple Street Bakery",
         font=("Segoe UI", 16, "bold")).pack()
root.mainloop()
Tkinter window showing a brown MS logo PNG above the bold text Maple Street Bakery
The 120 x 120 PNG logo in a Label.

To put text beside the image in one widget, use compound from my Tkinter Label tutorial.

Checkpoint: logo.width() returns 120.

Step 2: Load and resize a JPG image with Pillow

Pillow opens and shrinks the 1600 px JPG, and ImageTk.PhotoImage converts it for Tkinter:

import tkinter as tk
from PIL import Image, ImageTk

root = tk.Tk()
root.title("Blueberry Muffin")
root.geometry("600x400")

img = Image.open("muffin.jpg")
print("Original size:", img.size)
img.thumbnail((540, 340))        # shrink, keep the aspect ratio
print("Shown at:", img.size)

photo = ImageTk.PhotoImage(img)  # keep this reference
photo_label = tk.Label(root, image=photo)
photo_label.pack(pady=20)
root.mainloop()
Tkinter window showing a blueberry muffin JPG photo resized with Pillow to 510 by 340 pixels
The JPG shrunk to 510 x 340 with thumbnail().

thumbnail() keeps the aspect ratio and never enlarges; resize((w, h)) forces an exact size. To save converted files, see the JPG to PNG converter in Python.

Checkpoint: the console prints Shown at: (510, 340).

Step 3: Add Previous and Next buttons to swap the image

Swap the single photo for a menu list, show() and two buttons (same imports and window):

MENU = [("muffin.jpg", "Blueberry Muffin", 3.75),
        ("croissant.jpg", "Butter Croissant", 4.25),
        ("pie.jpg", "Apple Pie Slice", 5.50)]

photo_label = tk.Label(root)
photo_label.pack(pady=(16, 8))
caption = tk.Label(root, font=("Segoe UI", 13, "bold"))
caption.pack()

index = 0
photo = None    # keeps the current image alive

def show(i):
    global index, photo
    index = i % len(MENU)               # wrap around at both ends
    file, name, price = MENU[index]
    img = Image.open(file)
    img.thumbnail((540, 340))
    photo = ImageTk.PhotoImage(img)
    photo_label.config(image=photo)
    caption.config(text=f"{name} - ${price:.2f}  "
                        f"({index + 1} of {len(MENU)})")

buttons = tk.Frame(root)
buttons.pack(pady=12)
prev_btn = tk.Button(buttons, text="< Previous", width=12,
                     command=lambda: show(index - 1))
prev_btn.pack(side="left", padx=6)
next_btn = tk.Button(buttons, text="Next >", width=12,
                     command=lambda: show(index + 1))
next_btn.pack(side="left", padx=6)
Tkinter image viewer showing a blueberry muffin photo with the caption 1 of 3 and Previous and Next buttons boxed in red
First run: the muffin, its price and the two buttons.

config(image=photo) swaps the picture in place, and global photo keeps it alive after show() returns. % wraps the index. More in the Python Tkinter Button guide.

Test the Tkinter image viewer

Run it and press Previous on the first photo. That’s the edge case most likely to break:

Tkinter image viewer before and after pressing Previous: Blueberry Muffin 1 of 3 changes to Apple Pie Slice $5.50 3 of 3
Previous wrapped around to the last menu item.

Checkpoint: the caption reads Apple Pie Slice - $5.50 (3 of 3).

The window stays 600 x 480 for every photo; to change that, see how to set the Tkinter window size.

Why does a Tkinter image show blank or raise an error?

The image is blank. A PhotoImage held only by a local variable is deleted when the function returns:

root = tk.Tk()
root.title("Butter Croissant")
root.geometry("600x400")
photo_label = tk.Label(root, bg="#e2e8f0")
photo_label.pack(pady=20)
Before and after: a blank grey Tkinter label when the PhotoImage is garbage-collected, and the croissant photo once a reference is kept
Before: garbage-collected. After: photo_label.image = photo keeps it.

JPG fails with TclError. Plain tk.PhotoImage on Tk 8.6 can’t read JPG. The real run in VS Code:

import tkinter as tk

root = tk.Tk()
try:
    photo = tk.PhotoImage(file="muffin.jpg")
except tk.TclError as err:
    print("TclError:", err)

Output:

TclError: couldn't recognize data in image file "muffin.jpg"
VS Code running jpg_error.py and printing TclError couldn't recognize data in image file muffin.jpg
Open JPG files with Pillow instead.

ANTIALIAS is gone. Pillow 10 removed Image.ANTIALIAS:

from PIL import Image

img = Image.open("muffin.jpg")
small = img.resize((300, 200), Image.ANTIALIAS)

Output:

AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS'

Note: use Image.LANCZOS, the same filter under its current name.

Where to take it next: Tkinter image file dialogs and backgrounds

Add a file dialog, as in uploading a file in Python Tkinter, or put a photo behind a form by setting a background image in Tkinter.

More Tkinter reading from the site:

Frequently asked questions

How do I display an image in Tkinter?

Pass a tk.PhotoImage to a Label’s image= option and keep a reference.

Why can’t Tkinter open my JPG file?

Tk 8.6 reads PNG, GIF and PPM only. Use Pillow’s ImageTk.PhotoImage.

How do I resize an image in Tkinter?

Resize with Pillow first: thumbnail() or resize().

Why does my Tkinter image not show up?

It was garbage-collected. Keep it, for example label.image = photo.

How do I change the image in a Label?

Call label.config(image=new_photo) and keep the new reference.

Where is PhotoImage documented?

See tkinter images in the Python docs.