tkinter and library PhotoImage - python

This code works:
img = PhotoImage(file="Image.gif")
Label(root, image=img).pack()
How come this way doesn't work?
Label(root, image=PhotoImage(file="Image.gif")).pack()
Is it not possible to have everything in one line?

The problem isn't about syntax -- it's about garbage collection. In your shortened form:
Label(root, image=PhotoImage(file="Image.gif")).pack()
the pointer to the image returned by PhotoImage() never gets saved, so the image gets garbage collected and doesn't display. In your longer form:
img = PhotoImage(file="Image.gif")
Label(root, image=img).pack()
You're holding onto a pointer to the image, so everything works fine. You can convince yourself of this by wrapping the working code in a function and making img local to that function:
from tkinter import *
root = Tk()
def dummy():
img = PhotoImage(file="Image.gif")
Label(root, image=img).pack()
dummy()
mainloop()
Now, it won't display anymore because img disappears when the function returns and your image gets garbage collected. Now, return the image and save the returned value in a variable:
def dummy():
img = PhotoImage(file="Image.gif")
Label(root, image=img).pack()
return img
saved_ref = dummy()
And your image works again! The common fix for this looks something like:
def dummy():
img = PhotoImage(file="Image.gif")
label = Label(root, image=img)
label.image_ref = img # make a reference that persists as long as label
label.pack()
dummy()
But you can see we've moved a long way away from a one-liner!

On the first version, img keeps the reference to the image.
On the second version, there is not reference to that image and pack() returns None

Related

Tkinter image not showing

I made this piece of code:
from tkinter import *
from PIL import ImageTk, Image
import sys
import getnew
class startUp:
def __init__(self, master):
master.title("Tag checker")
master.resizable(False, False)
img1 = ImageTk.PhotoImage(Image.open("images/ss.png"))
cercaImg = Label(master, image = img1)
cercaImg.bind("<Button-1>",clicka)
cercaImg.grid(row=0,column=0)
img2 = ImageTk.PhotoImage(Image.open("images/opz.png"))
opzioniImg = Label(master, image = img2)
opzioniImg.grid(row=0,column=1)
img3 = ImageTk.PhotoImage(Image.open("images/exit.png"))
esciImg = Label(master, image = img3)
esciImg.bind("<Button-1>",(master.destroy and quit))
esciImg.grid(row=0,column=2)
def clicka(event):
print('ciaooo')
x = getnew.getSchools()
print(x[0][0],x[0][1],x[0][2])
root = Tk()
st = startUp(root)
root.mainloop()
The point is to have 3 images that, when clicked, execute a function, but he images don't show up. They do appear as size and 'clickable' zone and they execute the function, but the image as it is doesn't show up.
What am I doing wrong here ?
From tkinter docs on PhotoImage:
You must keep a reference to the image object in your Python program, either by storing it in a global variable, or by attaching it to another object.
The reason to do so is :
When a PhotoImage object is garbage-collected by Python (e.g. when you return from a function which stored an image in a local variable), the image is cleared even if it’s being displayed by a Tkinter widget.
To avoid this, the program must keep an extra reference to the image object. A simple way to do this is to assign the image to a widget attribute.
Hence for your program:
img1 = ImageTk.PhotoImage(Image.open("images/ss.png"))
cercaImg = Label(master, image = img1)
cercaImg.image = img1 # Keep a reference
Similarly for the other images as well.

Tkinter create_image

I've been working to get a simple Tkinter canvas to display an image using create_image. I've read many threads that say that you need to create a reference to the object outside any function or class, otherwise the image object will be garbage collected. Unfortunately, I still cannot get this to work. Below is my code as it stands. Ignore all the colors - I use them to illustrate where the frames and canvas live on the window.
-Kirk
import Tkinter as tk
from PIL import Image
from PIL import ImageTk
imageList = []
image = Image.open('len_std.jpg')
#event handlers
def hit_sel_click():
imageList = []
test_image = ImageTk.PhotoImage(image)
imageList.append(cnv_hits.create_image(0,0,
image=test_image))
#start root
root = tk.Tk()
root.title('SimView')
root.resizable(width=False, height=False)
#target/control variables
hit_sel = tk.StringVar() #holds radio button with activity level
#build GUI
frm_hits = tk.Frame(root, height=800, width=200, bg='#FF0000')
frm_hits.grid(rowspan=3, sticky=tk.W+tk.N+tk.S+tk.E)
tk.Label(frm_hits, text='Activity:').grid()
tk.Radiobutton(frm_hits, text='Low', variable=hit_sel, value='Low',
command=hit_sel_click).grid(sticky=tk.W)
tk.Radiobutton(frm_hits, text='Medium', variable=hit_sel, value='Medium',
command=hit_sel_click).grid(sticky=tk.W)
tmp = tk.Radiobutton(frm_hits, text='High', variable=hit_sel,value='High',
command=hit_sel_click)
tmp.grid(sticky=tk.W)
tmp.select()
frm_hit_list = tk.Frame(frm_hits, bg='#002288')
frm_hit_list.grid(sticky=tk.W+tk.N+tk.E+tk.S)
scrl_hits = tk.Scrollbar(frm_hit_list, orient=tk.VERTICAL)
scrl_hits.grid(row=0, column=1, sticky=tk.N+tk.S)
cnv_hits = tk.Canvas(frm_hit_list, bg='#888800',width=200, height=200,
yscrollcommand=scrl_hits.set)
cnv_hits.grid(row=0, column=0, sticky=tk.W+tk.N+tk.E+tk.S)
scrl_hits.config(command=cnv_hits.yview)
root.mainloop()
You are using test_image to draw the image of cnv_hits. That is right, but you forgot that test_image is local to hit_sel_click() method; which thing means it is not available to your main program.
To resolve this, you have 2 choices:
Either declare test_image as global inside hit_sel_click()
Or run test_image = ImageTk.PhotoImage(image) before you declare hit_sel_click().
Nota Bene:
For the first case, you will need to run root = tk.Tk() before hit_sel_click().
In case you choose the second option, you will need to run root = tk.Tk() before test_image = ImageTk.PhotoImage(image)
If you don't do this, your program will raise a RuntimeError exception.

Cannot construct tkinter.PhotoImage from PIL Image

I try to show a image in a label when I push a button, but the image are too large and I have tried to resize the image. I have created this function:
def image_resize(imageFile):
width = 500
height = 300
image = Image.open(imageFile)
im2 = image.resize((width, height), Image.ANTIALIAS)
return im2
To show the image I have created this function:
def show_image():
label_originalimage ['image'] = image_tk
And the button with the command=show_image:
filename = 'bild_1.jpg'
image_resize = image_resize(filename)
image_tk = PhotoImage(image_resize)
button_open = Button(frame_open, text='Open Image', command=show_image)
I get only this:
TypeError : __str__ returned non-string (type instance)
The PhotoImage class from tkinter takes a filename as an argument, and as it cannot convert the image into a string, it complains. Instead, use the PhotoImage class from the PIL.ImageTk module. This works for me:
from tkinter import *
from PIL import ImageTk, Image
def image_resize(imageFile):
width = 500
height = 300
image = Image.open(imageFile)
im2 = image.resize((width,height), Image.ANTIALIAS)
return im2
def show_image():
label_originalimage ['image'] = image_tk
root = Tk()
filename = './Pictures/Space/AP923487321702.jpg'
image_resize = image_resize(filename)
image_tk = ImageTk.PhotoImage(image_resize)
label_originalimage = Label(root)
label_originalimage.pack()
button_open = Button(root, text='Open Image', command=show_image)
button_open.pack()
root.mainloop()
Notice the change from image_tk = PhotoImage(image_resize) to image_tk = ImageTk.PhotoImage(image_resize).
I had the same problem when I try to construct a canvas image item for
tkinter from a tkinter PhotoImage. The latter was constructed from some
image data in memory (in my case an opencv image). The same exception
occurs if I simply try to convert the PhotoImage to a string.
I guess there is a bug in the conversion method __str__ of the PhotoImage,
making it simply returns the image source. If constructed from a file name
(see below) this works fine. If constructed from some image data, this is not
of type string and yields an exception.
Unfortunately, using the compatible PhotoImage from PIL's
ImageTk module like matsjoyce suggested didn't help me either because I experienced an even worse problem, probably a platform or library version dependent bug (I used OS X 10.11.6, python 3.5, tkinter 8.6, PIL 1.1.7): Now the python script crashed at the construction of the canvas image item with a "Bus Error".
The only workaround I am aware of was to store the image data into a temporary file and use a tkinter PhotoImage constructed from that file name. (Trying the same with the PIL PhotoImage still crashes.)
#!/usr/bin/env python3
import tkinter
import tempfile
import cv2
def opencv2photoimg(opencv_img):
"""Convert OpenCV (numpy) image to tkinter photo image."""
# ugly workaround: store as file & load file, because direct
# construction leads to a crash on my platform
tmpfile = tempfile.NamedTemporaryFile(suffix='.png', delete=True)
# ^^^ I am using PNGs only, you might want to use another suffix
cv2.imwrite(tmpfile.name, opencv_img)
return tkinter.PhotoImage(file=tmpfile.name)
# load image
img = cv2.imread('test.png')
# do something w/ the image ...
# setup tk window w/ canvas containing an image
root = tkinter.Tk()
canvas = tkinter.Canvas(root, width=img.shape[1], height=img.shape[0])
canvas.pack()
# keep reference to PhotoImage to avoid it being garbage collected
# (well known tkinter bug for canvas image items)
photo_img = opencv2photoimg(img)
# create a canvas item
img_item = canvas.create_image(0, 0, anchor=tkinter.NW, image=photo_img)
# display the window
tkinter.mainloop()
I do not think it's elegant, but it works.
Yes it works, but yeeeucchh - what I way to have to do it.
Surely there is a better way.
Here is my test code I got to starting from here....
import tkinter
from PIL import Image
import numpy
import time
import io
#python2 version (original) -> 120fps
#full physical file io and new image each cycle -> 130fps
#reuse PIL Image instead of create new each time -> 160fps
class mainWindow():
times=1
timestart=time.clock()
data=numpy.array(numpy.random.random((400,500))*100,dtype=int)
theimage = Image.frombytes('L', (data.shape[1],data.shape[0]),data.astype('b').tostring())
def __init__(self):
self.root = tkinter.Tk()
self.frame = tkinter.Frame(self.root, width=500, height=400)
self.frame.pack()
self.canvas = tkinter.Canvas(self.frame, width=500,height=400)
self.canvas.place(x=-2,y=-2)
self.root.after(0,self.start) # INCREASE THE 0 TO SLOW IT DOWN
self.root.mainloop()
def start(self):
global data
global theimage
self.theimage.frombytes(self.data.astype('b').tobytes())
self.theimage.save('work.pgm')
self.photo = tkinter.PhotoImage(file='work.pgm')
self.canvas.create_image(0,0,image=self.photo,anchor=tkinter.NW)
self.root.update()
self.times+=1
if self.times%33==0:
print("%.02f FPS"%(self.times/(time.clock()-self.timestart)))
self.root.after(10,self.start)
self.data=numpy.roll(self.data,-1,1)
if __name__ == '__main__':
x=mainWindow()
Here it is: I found that the input data to photoimage can be a byte array that looks like a ppm file, although it only appears to work on a subset of legal ppm (e.g. 16 bit values don't work)
So for future reference.......
import tkinter
import numpy
import time
#python2 version (original) -> 120fps
#full physical file io and new image each cycle -> 130fps
#reuse PIL Image instead of create new each time -> 160fps
#and... direct image into tkinter using ppm byte array -> 240 fps
class mainWindow():
times=1
timestart=time.clock()
data=numpy.array(numpy.random.random((400,500))*900,dtype=numpy.uint16)
def __init__(self):
self.root = tkinter.Tk()
self.frame = tkinter.Frame(self.root, width=500, height=400)
self.frame.pack()
self.canvas = tkinter.Canvas(self.frame, width=500,height=400)
self.canvas.place(x=-2,y=-2)
xdata = b'P5 500 400 255 ' + self.data.tobytes()
self.photo = tkinter.PhotoImage(width=500, height=400, data=xdata, format='PPM')
self.imid = self.canvas.create_image(0,0,image=self.photo,anchor=tkinter.NW)
self.root.after(1,self.start) # INCREASE THE 0 TO SLOW IT DOWN
self.root.mainloop()
def start(self):
global data
xdata = b'P5 500 400 255 ' + numpy.clip(self.data,0,255).tobytes()
self.photo = tkinter.PhotoImage(width=500, height=400, data=xdata, format='PPM')
if True:
self.canvas.itemconfig(self.imid, image = self.photo)
else:
self.canvas.delete(self.imid)
self.imid = self.canvas.create_image(0,0,image=self.photo,anchor=tkinter.NW)
self.times+=1
if self.times%33==0:
print("%.02f FPS"%(self.times/(time.clock()-self.timestart)))
self.root.update()
self.root.after(0,self.start)
self.data=numpy.roll(self.data,-1,1)
if __name__ == '__main__':
x=mainWindow()

tkinter: why images are not dysplayed correctly

The code:
from tkinter import *
root = Tk()
f1=Frame(root)
for img,rlf in [ ('woman',RAISED),('mensetmanus',SOLID),
('terminal',SUNKEN), ('escherknot',FLAT),
('calculator',GROOVE),('letters',RIDGE)]:
filename = img + ".gif"
img1 = PhotoImage(file= filename)
Label(f1, image = img1, relief=rlf).pack(side=LEFT,
padx=5)
f1.pack()
root.mainloop()
Could you help me understand why this excerpt produces 5 empty places for images (though borders are drawn correctly according to what was meant), and 1 image. The last image (which is visible) is letters. And it seems to be cropped from than its actual size. My letters.gif contains letters from A to G, but this code displays only from half B to half F.
It does not work, i think, because img1 is overwritten in each loop. You need to keep references to image objects somewhere, so that garbage collector wont trash them:
from tkinter import *
root = Tk()
f1=Frame(root)
img_list = [] #<-- store references to images
for img,rlf in [ ('woman',RAISED),('mensetmanus',SOLID),
('terminal',SUNKEN), ('escherknot',FLAT),
('calculator',GROOVE),('letters',RIDGE)]:
filename = img + ".gif"
img1 = PhotoImage(file= filename)
img_list.append(img1) #<-- store references to images
Label(f1, image = img1, relief=rlf).pack(side=LEFT,
padx=5)
f1.pack()
root.mainloop()

tkinter canvas image not displaying

I have a simple canvas being created in a function, and i would like an image displayed on the canvas.
def start(root):
startframe = tkinter.Frame(root)
canvas = tkinter.Canvas(startframe,width=1280,height=720)
startframe.pack()
canvas.pack()
one = tkinter.PhotoImage('images\one.gif')
canvas.create_image((0,0),image=one,anchor='nw')
when i run the code i get a blank 1280x720 window, no image.
i have looked at the following website: http://effbot.org/pyfaq/why-do-my-tkinter-images-not-appear.htm but i do not understand how to apply their example to my situation (i dont know what to create a reference to or how to create a reference, if that is my problem). I have also looked at some stack overflow questions but they did not help either.
Escape backslashes in path string correctly. (or use r'raw string literal').
Prevent PhotoImage object being garbage collected.
specify the filename using file=... option.
def start(root):
startframe = tkinter.Frame(root)
canvas = tkinter.Canvas(startframe,width=1280,height=720)
startframe.pack()
canvas.pack()
# Escape / raw string literal
one = tkinter.PhotoImage(file=r'images\one.gif')
root.one = one # to prevent the image garbage collected.
canvas.create_image((0,0), image=one, anchor='nw')
UPDATE
The two statements one = ... and root.one = one can be merged into one statement:
root.one = one = tkinter.PhotoImage(r'images\one.gif')
How about canvas.update()? I was suffering a similar problem. I am using grid, so instead of .pack I needed to use .update.
I had the situation when image didn`t show up, when I call it in function, but otherwise everything was okay. That is my function
def ask_for_file():
f_types = [('PNG Images', '*.png')]
filename = askopenfilename(filetypes=f_types)
img = ImageTk.PhotoImage(file=filename)
canvas.config(height=img.height(), width=img.width())
canvas.create_image(0, 0, anchor=NW, image=img)
My solution was to add img = None as global variable and change it inside function. It worked
img = None
def ask_for_file():
global img
f_types = [('PNG Images', '*.png')]
filename = askopenfilename(filetypes=f_types)
img = ImageTk.PhotoImage(file=filename)
canvas.config(height=img.height(), width=img.width())
canvas.create_image(0, 0, anchor=NW, image=img)

Categories