86

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()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
thomas.winckell
  • 1,217
  • 1
  • 10
  • 16
  • 3
    http://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. – maszoka Jan 31 '21 at 01:26
  • @maszoka: `effbot.org` may be down, but you can still read the link [Why do my Tkinter images not appear?](https://web.archive.org/web/20201111190625id_/http://effbot.org/pyfaq/why-do-my-tkinter-images-not-appear.htm) thanks to the Internet Archive [wayback machine](https://archive.org/web/web.php). – martineau Oct 05 '21 at 13:35
  • 1
    Also note that the same problem can appear anywhere temporary `PhotoImage`s are used, for example in a calling sequence such as `label = Label(image=ImageTk.PhotoImage(Image.fromarray(data)))`. – martineau Feb 26 '22 at 15:51

5 Answers5

120

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? (The FAQ answer is currently not outdated)

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • 13
    Wow. Do they consider this a bug in tkinter? They should. – Tamas Hegedus Dec 26 '20 at 23:12
  • 4
    I found a very old ticket, already closed without a fix: https://bugs.python.org/issue632323 – Tamas Hegedus Dec 26 '20 at 23:19
  • 2
    The link is not working atm, is there another way than using "global"? – Aru Jan 19 '21 at 19:15
  • 3
    @aru: This example shows how to do it without using a global. – Bryan Oakley Jan 19 '21 at 19:18
  • 1
    @Aru: FYI, I just fixed the broken link. – martineau Mar 15 '21 at 16:53
  • 10
    @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. – martineau Mar 15 '21 at 17:03
  • 1
    Anyhow, feels weird that `effbot` says that `tkinter` widget keeps reference to the internal object because I do not think that we can retrieve back the `ImageTk` object that they said is referenced by a widget. – Delrius Euphoria Jul 20 '22 at 13:14
  • Do you have to use this approach if your class is a subclass of PhotoImage? Do you still need to save the PhotoImage? – ᴇɴᴅᴇʀᴍᴀɴ Aug 16 '22 at 02:38
  • 1
    @Enderman: a reference needs to be kept by something, somewhere. – Bryan Oakley Aug 16 '22 at 15:02
  • But won’t it be saved as self in the class? – ᴇɴᴅᴇʀᴍᴀɴ Aug 16 '22 at 18:44
  • 2
    @Enderman: no, because it's referencing itself. An object needs an external reference to it or the garbage collector might destroy it. – Bryan Oakley Aug 16 '22 at 18:56
  • Umm so should I save the PhotoImage.__init__() function as a variable? Does that even work and would that save the PhotoImage correctly? – ᴇɴᴅᴇʀᴍᴀɴ Aug 16 '22 at 18:58
  • 1
    @Enderman: no. You need to save a reference to the instance of the class. – Bryan Oakley Aug 16 '22 at 19:48
  • Ah. Thanks @BryanOakley! Appreciate the help! (I am not a professional coder yet lol) – ᴇɴᴅᴇʀᴍᴀɴ Aug 17 '22 at 05:36
  • Why wouldn't *the canvas object* maintain a reference to the image, in the same way that the master Tk instance is maintaining a reference to the canvas (which is *also* a local within `__init__` here)? – Karl Knechtel Mar 30 '23 at 00:28
  • @KarlKnechtel: because the canvas object exists inside the embedded tcl interpreter. The tcl interpreter doesn't know anything about python objects. Similarly, python doesn't know that the embedded tcl interpreter has a reference to the image. – Bryan Oakley Mar 30 '23 at 00:39
  • I mean, in terms of tcl's internal structure, how come its internal representation of the window has some kind of reference to the canvas, but the canvas doesn't have any kind of reference to the image? Or are we saying that Python GCing its `PhotoImage` object is incorrectly telling tcl to free the memory for its own internal representation of the image? In that case, why doesn't it crash with a dangling pointer? – Karl Knechtel Mar 30 '23 at 00:49
9
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

TIRTH SHAH
  • 109
  • 1
  • 2
  • 3
    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. – Sylvester Kruin Jan 07 '22 at 16:44
8

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:

Faraaz Kurawle
  • 1,085
  • 6
  • 24
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)
Thingamabobs
  • 7,274
  • 5
  • 21
  • 54
0

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

Gabriel
  • 37
  • 3