92

This code works:

import tkinter

root = tkinter.Tk()
canvas = tkinter.Canvas(root)
canvas.grid(row = 0, column = 0)
photo = tkinter.PhotoImage(file = './test.gif')
canvas.create_image(0, 0, image=photo)
root.mainloop()

It shows me the image.

Now, this code compiles but it doesn't show me the image, and I don't know why, because it's the same code, in a class:

import tkinter

class Test:
    def __init__(self, master):
        canvas = tkinter.Canvas(master)
        canvas.grid(row = 0, column = 0)
        photo = tkinter.PhotoImage(file = './test.gif')
        canvas.create_image(0, 0, image=photo)

root = tkinter.Tk()
test = Test(root)
root.mainloop()
3
  • 3
    effbot.org is down. The gist of it is that the image is passed by reference. If the reference is to a local variable, the memory referenced gets reused and the reference becomes stale. The variable storing the image should be in the same scope (has to have the same lifetime) as the Tk gui object it appears on. Commented Jan 31, 2021 at 1:26
  • @maszoka: effbot.org may be down, but you can still read the link Why do my Tkinter images not appear? thanks to the Internet Archive wayback machine. Commented Oct 5, 2021 at 13:35
  • 1
    Also note that the same problem can appear anywhere temporary PhotoImages are used, for example in a calling sequence such as label = Label(image=ImageTk.PhotoImage(Image.fromarray(data))). Commented Feb 26, 2022 at 15:51

5 Answers 5

126

The variable photo is a local variable which gets garbage collected after the class is instantiated. The solution involves saving a reference to the photo, for example:

self.photo = tkinter.PhotoImage(...)

If you do a Google search on "tkinter image doesn't display", the first result is this:

Why do my Tkinter images not appear?

Sign up to request clarification or add additional context in comments.

17 Comments

Wow. Do they consider this a bug in tkinter? They should.
I found a very old ticket, already closed without a fix: bugs.python.org/issue632323
The link is not working atm, is there another way than using "global"?
@aru: This example shows how to do it without using a global.
@TamasHegedus: I agree it's bug, but apparently not one that anyone has ever bothered to fix after (currently) nearly two decades. Have lost count how many times I see a question regarding to it still pops up.
|
11
from tkinter import *
from PIL import ImageTk, Image

root = Tk()

def open_img():
    global img
    path = r"C:\.....\\"
    img = ImageTk.PhotoImage(Image.open(path))
    panel = Label(root, image=img)
    panel.pack(side="bottom", fill="both")
but1 = Button(root, text="click to get the image", command=open_img)
but1.pack()
root.mainloop() 

Just add global to the img definition and it will work

1 Comment

This answer is fine for a program that just uses functions, but if, as in the OP's case, you use a class, than global is not the way to go.
9

The problem is Python automatically deletes the references to the variable by a process known as Garbage Collection. The solution is to save the reference or to create a new reference.

The following are the ways:

  1. Using self to increase the reference count and to save the reference.
import tkinter

class Test:
    def __init__(self, master):
        canvas = tkinter.Canvas(master)
        canvas.grid(row = 0, column = 0)
        self.photo = tkinter.PhotoImage(file = './test.gif') # Changes here
        canvas.create_image(0, 0, image=self.photo) # Changes here

root = tkinter.Tk()
test = Test(root)
root.mainloop()
  1. Saving it to a list to increase the reference count and to save the reference.
import tkinter
l=[]
class Test:

    def __init__(self, master):
        canvas = tkinter.Canvas(master)
        canvas.grid(row = 0, column = 0)
        photo = tkinter.PhotoImage(file = './test.gif')
        l.append(photo)
        canvas.create_image(0, 0, image=photo)

root = tkinter.Tk()
test = Test(root)
root.mainloop()

While using method 2, you can either make a global list as i did or use list inside the class. Both would work.

Some useful links:

Comments

2

As a rule of thumb, whenever you create your image in an indented block of code you need to safe a reference to that image. This is because of the python's automated garbage collection and it collects everything with a refcount of 0 when it destroys/leaves that frame/page/indented block of code.


The canonical way to deal with it is to have a list of images somewhere in the global namespace and add your image-references to that list. This is convenient but not very efficient and should be used for small applications.

import tkinter as tk

global_image_list = []
global_image_list.append(tk.PhotoImage(file = 'test.png'))

An more efficient way is to bound an attribute to your widget or class that holds that reference for you, as Bryan proposed in his answer. It doesn't make a difference if you do self.image or widget.image that was assigned widget = tk.Widget(.. before. But this also might not the right approach if you want to use that image further even when the widget is destroyed and garbage collected.

import tkinter as tk

root = tk.Tk()
label = tk.Label(root, text='test')
label.image = tk.PhotoImage(file = 'test.png')
label.configure(image=label.image)

Comments

0

Just add global photo as the first line inside the function.

2 Comments

And then you create a 2nd Test instance and the first instance loses its image. Congratulations.
There is definitely no no need to use global when in a class. self. takes care of all of that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.