Display an image with Tkinter - python

I am trying to use Tkinter to display an image in Python 2.7. Below is an example of a code I tried to use, which displays all images in the folder (from
http://code.activestate.com/recipes/521918-pil-and-tkinter-to-display-images/ ):
import os, sys
import Tkinter
import Image, ImageTk
def button_click_exit_mainloop (event):
event.widget.quit() # this will cause mainloop to unblock.
root = Tkinter.Tk()
root.bind("<Button>", button_click_exit_mainloop)
root.geometry('+%d+%d' % (100,100))
dirlist = os.listdir('.')
old_label_image = None
for f in dirlist:
try:
image1 = Image.open(f)
root.geometry('%dx%d' % (image1.size[0],image1.size[1]))
tkpi = ImageTk.PhotoImage(image1)
label_image = Tkinter.Label(root, image=tkpi)
label_image.place(x=0,y=0,width=image1.size[0],height=image1.size[1])
root.title(f)
if old_label_image is not None:
old_label_image.destroy()
old_label_image = label_image
root.mainloop() # wait until user clicks the window
except Exception, e:
# This is used to skip anything not an image.
# Image.open will generate an exception if it cannot open a file.
# Warning, this will hide other errors as well.
pass
For instance, a cross (top) will lead to the following displayed image in the Tkinter window (bottom):
I have tried other codes (including some to display live my webcam), they all lead to the same result: I can see an image, it has the correct dimension, but each column has a constant pixel value (the one of the corresponding column of the first row of the original image).

Related

Black sometimes replaces transparent when using PIL on tkinter

I have been trying to display several images (in png) on tkinter. All of them have a transparent background, but for some reason jailer.png (the handcuffs) has been displaying with a black background (see screenshot), while every other image works as it should. Why ?
Here's my code (if create_a_label_with_an_image is redudant, it's because i had a lot of problem wth garbage disposal, if anyone knows the trick, pls tell me !) :
import tkinter as tk
from tkinter import ttk
from os import walk
from PIL import Image, ImageTk
class Gui:
def __init__(self):
self.frames={"main":tk.Tk()} #every frame will be added in this dictionnary
self.l_widgets={} #all widgets
def create_notebook(self, name, config_args={}, grid_args={}, frame="main"):
self.l_widgets[name] = (ttk.Notebook(self.frames[frame], **config_args),0)
self.l_widgets[name][0].grid(**grid_args)
def create_a_label_with_an_image(self, name, filename, size=(None, None), config_args={}, grid_args={}, frame="main"):
image_temp=Image.open(filename)
if size!=(None,None):
image_temp.thumbnail(size)
image=ImageTk.PhotoImage(image_temp)
self.l_widgets[name] = (ttk.Label(self.frames[frame]),image) #Image is stored at several places to hope it will not get erased
self.l_widgets[name][0].config(**config_args, image=image)
self.l_widgets[name][0].image=image
self.l_widgets[name][0].grid(**grid_args)
def add_to_notebook(self, name, name_frame, config_args={}, grid_args={}):
self.frames[name_frame]=ttk.Frame(self.frames["main"])
self.l_widgets[name][0].add(self.frames[name_frame], **config_args)
self.l_widgets[name][0].grid(**grid_args)
#END ####
g=Gui()
#here, i get all path to icons from this directory
mypath = "./Wolvesville Notebook/icons/characters"
filenames = next(walk(mypath), (None, None, []))[2] # [] if no file
if filenames==[]:
raise Warning("no icons found")
g.create_notebook("roles")
g.add_to_notebook("roles","all", config_args={"text":"all"})
nbr_images=0
for icon in filenames:
g.create_a_label_with_an_image(icon[:-4], mypath+"/"+icon,
size=(50,50),
grid_args={"row":nbr_images//12,
"column":nbr_images%12},
frame=str(pool))
nbr_images+=1
tk.mainloop()
Here's what the final result look like : Notebook, with one onglet named all, and 5 icons, all transparent but handcuffs
Here is the file that doesn't work : jailer.png.
Thank you in advance for the help !
Check the format of PNG file first, convert to "RGBA" if not.
>>> from PIL import Image
>>> im = Image.open('d:/jailer.png')
>>> im.mode
'LA'
>>> im.getpixel((0, 0))
(0, 0)
>>> new_im = im.convert(mode="RGBA")
>>> new_im.save("D:/jailer_new.png", format="PNG")
Simple code to show the differece, left one is your original copy.
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
im1 = Image.open("D:/jailer.png")
im2 = Image.open("D:/jailer_new.png")
pic1 = ImageTk.PhotoImage(im1)
pic2 = ImageTk.PhotoImage(im2)
label_1 = Label(root, image=pic1, bg='blue')
label_2 = Label(root, image=pic2, bg='blue')
label_1.pack(side='left')
label_2.pack(side='left')
root.mainloop()
AKX commented under my question to convert the grayscale + alpha into rgb + alpha, and it works ! I don't know why it was in grayscale, but it doesn't matter. I will close the subject, thanks !
PS : I can't seem to put a comment as the answer, so I wrote another one.

My image won't show but the resolution of my image is applied

I'm learning the tkinter module and I got a problem. I did a fonction to show an image from my folder but my image won't show up but the resolution of the window is the same as my image. Anybody have a solution ?
from tkinter import *
from PIL import ImageTk, Image
root = Tk()
def my_image(img, row, column) :
pic = ImageTk.PhotoImage(Image.open("C:/Users/Mark/Downloads/test/" + img))
my_img = Label(image=pic)
return my_img.grid(row=row, column=column)
# Create image
image1 = my_image("luigi_icon.png", 0, 1)
root.mainloop()
This problem was many times on Stackoverflow.
There is bug in PhotoImage which removes image from memory when it is assigned to local variable. You have to assign it to global variable or to some object
Common solution is to assign pic to label which displays it.
my_img.pic = pic
See Note at the end of page PhotoImage
(it is archived version on Wayback Machine because original version was removed)
BTW:
You have other problem. grid() gives None. You should return my_img if you want to access label later (ie. to replace image).
Currently original tk.PhotoImage can work with png so you don't need PIL. But if you want to work with some less popular image format then you may need pillow.
import os
#from tkinter import *
import tkinter as tk # PEP8: `import *` is not preferred
from PIL import ImageTk, Image
# --- functions ---
def my_image(img, row, column) :
fullpath = os.path.join("C:/Users/Mark/Downloads/test/", img)
#pic = ImageTk.PhotoImage(Image.open(fullpath))
# or
#pic = ImageTk.PhotoImage(file=fullpath)
# or even without `PIL/pillow` for `png`
pic = tk.PhotoImage(file=fullpath)
my_img = tk.Label(image=pic)
my_img.grid(row=row, column=column)
my_img.pic = pic # solution for bug in `PhotoImage`
return my_img
# --- main ---
root = tk.Tk()
image1 = my_image("luigi_icon.png", 0, 1)
root.mainloop()

python: tkinter switch svg content

I have a variety of SVG images, their filenames are in uniformed number format, such as 1.svg, 2.svg. I want to rename each file based on image content.
I coded a python tkinter app for me to do this, which it has a previous and next button and a text area, which allows me to go back and go previous and edit the filename.
Currently I succeed in displaying svg content by writing svg content into a temporary png file, then display the png file. However, I had trouble switching svg content.
Please have a look of following code:
# renamer.py
'''sh
pip install svglib
pip install Pillow
pip install tkinter
pip install reportlib
pip install lxml
'''
import os
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPDF, renderPM
from tkinter import *
from PIL import Image, ImageTk
# rename file by abs path
def rename(abs_path, new_name):
os.rename(abs_path, new_name)
tk = Tk()
# ---------------------------------------- fill your path here
# SVG directory absolute path
directory = "";
svg_paths = []
# iterate .svg file and append to collector
for filename in os.listdir(directory):
svg_paths.append(os.path.join(directory, filename));
# current manipulating index
at = 1
# feedback
# how many finished
# how many to go
def percent():
return f"{at} / {len(svg_paths)}"
vl = StringVar(tk, value=percent())
def go_previous():
global at
at -= 1
if at == 0:
# no more previous
at += 1
else:
vl.set(percent())
def go_next():
global at
at += 1
if at == len(svg_paths):
# no more next
at -= 1
else:
vl.set(percent())
label = Label(tk, textvariable=vl, font="Helvetica 30")
label.pack()
# tkinter doesnot support svg, therefore create a temp.png file
# in same level as SVG folder as converted png file to display
canvas = Canvas(tk, width=500, height=500)
canvas.pack()
last_img = None
'''
update png is achieved by Tkinter.Canvas widget
canvas.delete() => delete last image content
canvas.create_image() => set up new image content
the file name is always temp.png however the content is different
since new svg will be overwritten into the same file name
'''
'''
if you call following function at the end, it doesnot work,
which the png will not be displayed on tkinter
but if we exec these code in global scope instead of function
scope, it turns out working! I must use the function to update
the image content when either previous or next button is clicked
'''
def refresh_image():
global last_img, canvas
if last_img is not None:
canvas.delete(last_img)
drawing = svg2rlg(svg_paths[at-1])
renderPM.drawToFile(drawing, "temp.png", fmt="PNG")
img = Image.open('temp.png')
img = img.resize((400, 400), Image.ANTIALIAS)
pimg = ImageTk.PhotoImage(img)
last_img = canvas.create_image(0, 0, anchor='nw',image=pimg)
# display file name in input box
va = StringVar(tk, value='')
entry = Entry(tk, textvariable=va, font="Helvetica 30")
entry.pack(ipady=3)
# update file name
def refresh_entry():
va.set(svg_paths[at-1].split('/')[-1])
# rename file
def assign_name(new_name):
nm = directory + '/' + new_name
rename(svg_paths[at-1], nm)
# update recorded paths
svg_paths[at] = nm
previous = Button(tk, text="previous", command=go_previous)
_next = Button(tk, text="next", command=go_next)
previous.pack()
_next.pack()
refresh_image()
'''
As I mentioned in refresh_image(),
if you hash the function above and unhash the code below,
it works.
I want to update image content according to svg_paths at `at` index
'''
# drawing = svg2rlg(svg_paths[at-1])
# renderPM.drawToFile(drawing, "temp.png", fmt="PNG")
# img = Image.open('temp.png')
# img = img.resize((400, 400), Image.ANTIALIAS)
# pimg = ImageTk.PhotoImage(img)
# last_img = canvas.create_image(0, 0, anchor='nw',image=pimg)
refresh_entry()
tk.mainloop()
What I want to do, is whenever I call refresh_image() function, tkinter canvas change the svg content based on at index, which rewrite current svg file into the temp.png file and display the png.
However, I find it wired that, it works if I write the inner part of refresh_image() function to global scope, but if it's in functional scope, it stops working!
the code example I provided above does not work, which it calls refresh_image() function. The following is the working version, instead of calling the function, I executed the code block in refresh_image() once at the end:
'''sh
pip install svglib
pip install Pillow
pip install tkinter
pip install reportlib
pip install lxml
'''
import os
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPDF, renderPM
from tkinter import *
from PIL import Image, ImageTk
# rename file by abs path
def rename(abs_path, new_name):
os.rename(abs_path, new_name)
tk = Tk()
# ---------------------------------------- fill your path here
# SVG directory absolute path
directory = "";
svg_paths = []
# iterate .svg file and append to collector
for filename in os.listdir(directory):
svg_paths.append(os.path.join(directory, filename));
# current manipulating index
at = 1
# feedback
# how many finished
# how many to go
def percent():
return f"{at} / {len(svg_paths)}"
vl = StringVar(tk, value=percent())
def go_previous():
global at
at -= 1
if at == 0:
# no more previous
at += 1
else:
vl.set(percent())
def go_next():
global at
at += 1
if at == len(svg_paths):
# no more next
at -= 1
else:
vl.set(percent())
label = Label(tk, textvariable=vl, font="Helvetica 30")
label.pack()
# tkinter doesnot support svg, therefore create a temp.png file
# in same level as SVG folder as converted png file to display
canvas = Canvas(tk, width=500, height=500)
canvas.pack()
last_img = None
'''
update png is achieved by Tkinter.Canvas widget
canvas.delete() => delete last image content
canvas.create_image() => set up new image content
the file name is always temp.png however the content is different
since new svg will be overwritten into the same file name
'''
'''
if you call following function at the end, it doesnot work,
which the png will not be displayed on tkinter
but if we exec these code in global scope instead of function
scope, it turns out working! I must use the function to update
the image content when either previous or next button is clicked
'''
# def refresh_image():
# global last_img, canvas
# if last_img is not None:
# canvas.delete(last_img)
# drawing = svg2rlg(svg_paths[at-1])
# renderPM.drawToFile(drawing, "temp.png", fmt="PNG")
# img = Image.open('temp.png')
# img = img.resize((400, 400), Image.ANTIALIAS)
# pimg = ImageTk.PhotoImage(img)
# last_img = canvas.create_image(0, 0, anchor='nw',image=pimg)
# display file name in input box
va = StringVar(tk, value='')
entry = Entry(tk, textvariable=va, font="Helvetica 30")
entry.pack(ipady=3)
# update file name
def refresh_entry():
va.set(svg_paths[at-1].split('/')[-1])
# rename file
def assign_name(new_name):
nm = directory + '/' + new_name
rename(svg_paths[at-1], nm)
# update recorded paths
svg_paths[at] = nm
previous = Button(tk, text="previous", command=go_previous)
_next = Button(tk, text="next", command=go_next)
previous.pack()
_next.pack()
# refresh_image()
'''
As I mentioned in refresh_image(),
if you hash the function above and unhash the code below,
it works.
I want to update image content according to svg_paths at `at` index
'''
drawing = svg2rlg(svg_paths[at-1])
renderPM.drawToFile(drawing, "temp.png", fmt="PNG")
img = Image.open('temp.png')
img = img.resize((400, 400), Image.ANTIALIAS)
pimg = ImageTk.PhotoImage(img)
last_img = canvas.create_image(0, 0, anchor='nw',image=pimg)
refresh_entry()
tk.mainloop()
Please follow the testing instruction:
I have put the code and 3 svg file examples in github, run following command
git clone https://github.com/Weilory/svg_renamer
if you haven't install the packages:
pip install svglib
pip install Pillow
pip install tkinter
pip install reportlib
pip install lxml
direct terminal to directory containing SVG folder and renamer.py, open renamer.py, assign directory variable to the absolute path of your SVG folder.
run the code through terminal
python renamer.py
the renamer.py on github is the functional scope, which you should see NO image displaying in tkinter canvas.
hash the refresh_image() function and the calling part, unhash the global single execution part at the end of file, run again.
python renamer.py
the image will show up. but without calling the function, I won't be able to switch svg content.
This issue has been annoying me all day, I will be so appreciated if someone offers me a solution or some suggestions.
after so many trials, the issue occurs at pimg = ImageTK.PhotoImage(img), which for some reason, if ImageTK.PhotoImage() doesn't return a value at global scope, the data will be forgotten, therefore, if we declare pimg as a global variable and assigning value in refresh_image() function it will work.
'''sh
pip install svglib
pip install Pillow
pip install tkinter
pip install reportlib
pip install lxml
'''
import os
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPDF, renderPM
from tkinter import *
from PIL import Image, ImageTk
# rename file by abs path
def rename(abs_path, new_name):
os.rename(abs_path, new_name)
tk = Tk()
# ---------------------------------------- fill your path here
# SVG directory absolute path
directory = "C:/Users/QINY/OneDrive - Kardinia International College/Desktop/svg_conduct/SVG";
svg_paths = []
# iterate .svg file and append to collector
for filename in os.listdir(directory):
svg_paths.append(os.path.join(directory, filename));
# current manipulating index
at = 1
# feedback
# how many finished
# how many to go
def percent():
return f"{at} / {len(svg_paths)}"
vl = StringVar(tk, value=percent())
def go_previous():
global at
at -= 1
if at == 0:
# no more previous
at += 1
else:
vl.set(percent())
def go_next():
global at
at += 1
if at == len(svg_paths):
# no more next
at -= 1
else:
vl.set(percent())
label = Label(tk, textvariable=vl, font="Helvetica 30")
label.pack()
# tkinter doesnot support svg, therefore create a temp.png file
# in same level as SVG folder as converted png file to display
canvas = Canvas(tk, width=500, height=500)
canvas.pack()
last_img = None
pimg = None
'''
update png is achieved by Tkinter.Canvas widget
canvas.delete() => delete last image content
canvas.create_image() => set up new image content
the file name is always temp.png however the content is different
since new svg will be overwritten into the same file name
'''
'''
if you call following function at the end, it doesnot work,
which the png will not be displayed on tkinter
but if we exec these code in global scope instead of function
scope, it turns out working! I must use the function to update
the image content when either previous or next button is clicked
'''
def refresh_image():
global last_img, canvas, renderPM, Image, ImageTk, tk, pimg
if last_img is not None:
canvas.delete(last_img)
drawing = svg2rlg(svg_paths[at-1])
renderPM.drawToFile(drawing, "temp.png", fmt="PNG")
img = Image.open('temp.png')
img = img.resize((400, 400), Image.ANTIALIAS)
img.save('temp.png')
pimg = ImageTk.PhotoImage(img)
last_img = canvas.create_image(0, 0, anchor='nw',image=pimg)
img.close()
# display file name in input box
va = StringVar(tk, value='')
entry = Entry(tk, textvariable=va, font="Helvetica 30")
entry.pack(ipady=3)
# update file name
def refresh_entry():
va.set(svg_paths[at-1].split('/')[-1])
# rename file
def assign_name(new_name):
nm = directory + '/' + new_name
rename(svg_paths[at-1], nm)
# update recorded paths
svg_paths[at] = nm
previous = Button(tk, text="previous", command=go_previous)
_next = Button(tk, text="next", command=go_next)
previous.pack()
_next.pack()
refresh_image()
'''
As I mentioned in refresh_image(),
if you hash the function above and unhash the code below,
it works.
I want to update image content according to svg_paths at `at` index
'''
""" functional scope """
# drawing = svg2rlg(svg_paths[at-1])
# renderPM.drawToFile(drawing, "temp.png", fmt="PNG")
# img = Image.open('temp.png')
# img = img.resize((400, 400), Image.ANTIALIAS)
# img.save('temp.png')
# pimg = ImageTk.PhotoImage(img)
# last_img = canvas.create_image(0, 0, anchor='nw',image=pimg)
""" roll """
# img.close()
# canvas.delete(last_img)
# drawing = svg2rlg(svg_paths[at])
# renderPM.drawToFile(drawing, "temp.png", fmt="PNG")
# img = Image.open('temp.png')
# img = img.resize((400, 400), Image.ANTIALIAS)
# pimg = ImageTk.PhotoImage(img)
# last_img = canvas.create_image(0, 0, anchor='nw',image=pimg)
refresh_entry()
tk.mainloop()
I don't know why this happens but, be care when working with Pillow module, because some of the functions such as ImageTK requires assigning value to a global variable, in other words, if the class or function is initiated or declared only in functional scope, the data will be forgotten
if someone can explain why this happens, I would love to accept it as an answer.
Thanks for your time.

How do i properly attach a .PNG file into a TKINTER button?

I am doing a battleship game and a function below that executes to create a new button with an explosion image as it's background. I am using Mac & python 3.7
global redraw_gameboard
global Player
global AI_player
script_dir = os.path.dirname(__file__)
rel_path = "explode.png"
image = ImageTk.PhotoImage(file=os.path.join(script_dir, rel_path))
new_button = Button(redraw_gameboard,
height = 2,
width = 4,
command= already_shot,
image=image)
new_button.grid(row = row, column = column)
This is what is coming out:
I am not sure what you expect, since I don't know what does the "explode.png" image look like. Also, when asking questions on stackoverflow, please always try to post a minimal reproducible example.
However, as I understand, the problem probably comes from the fact that the image is bigger than the button, and it is cropped. Then, only the upper left part of the image is displayed in your buttons.
Suggested solution:
(You will need to install the pillow package if it is not done yet)
import os
from PIL import Image, ImageTk
import tkinter
# Sizes in pixels
BUTTON_HEIGHT = 40
BUTTON_WIDTH = 40
root = tkinter.Tk()
script_dir = os.path.dirname(__file__)
rel_path = "explode.png"
image = Image.open(os.path.join(script_dir, rel_path))
image = image.resize((BUTTON_WIDTH,BUTTON_HEIGHT))
imtk = ImageTk.PhotoImage(image)
# Using a void image for other buttons so that the size is given in pixels too
void_imtk = tkinter.PhotoImage(width=BUTTON_WIDTH, height=BUTTON_HEIGHT)
def create_button(row, column, im):
new_button = tkinter.Button(root,
height = BUTTON_HEIGHT,
width = BUTTON_WIDTH,
image=im)
new_button.grid(row = row, column = column)
create_button(0,0, imtk)
create_button(0,1, void_imtk)
create_button(1,0, void_imtk)
create_button(1,1, imtk)
root.mainloop()
Of course, you will want to make some changes for your program, e.g. using your widget architecture.

Analyzing a Sequence of Images using PIL/Tkinter

I'm currently trying to setup a GUI in Tkinter so that I can show a sequence of images (named file01.jpg, file02.jpg, etc. etc.). Currently I'm doing it by creating a Sequence object to manage the list of images that I care about:
class Sequence:
def __init__(self,filename,extension):
self.fileList = []
#takes the current directory
listing = os.listdir(os.getcwd())
#and makes a list of all items in that directory that contains the filename and extension
for item in listing:
if filename and extension in item:
self.fileList.append(item)
#and then sorts them into order
self.fileList.sort()
print self.fileList
def nextImage(self):
#returns a string with the name of the next image
return self.fileList.pop(0)
And then I'm using a rather simple Tkinter script I found online to generate the window and place an image there:
window = Tkinter.Tk()
window.title('Image Analysis!')
sequence = Sequence('test','jpg')
image = Image.open("test01.jpg")
image = image.convert('L')
imPix = image.load()
canvas = Tkinter.Canvas(window, width=image.size[0], height=image.size[1])
canvas.pack()
image_tk = ImageTk.PhotoImage(image)
canvas.create_image(image.size[0]//2, image.size[1]//2, image=image_tk)
window.bind("<space>", lambda e: nextFrame(sequence_object=sequence,event=e))
Tkinter.mainloop()
where nextFrame is defined as:
def nextFrame(sequence_object,event=None):
nextImage = sequence_object.nextImage()
print 'Next Image is: ',nextImage
image = Image.open(nextImage)
image = image.convert('L')
imPix = image.load()
image_tk = ImageTk.PhotoImage(image)
canvas.create_image(image.size[0]//2, image.size[1]//2, image=image_tk)
canvas.update()
In my python buffer I see the correct image sequence pop up ('Next Image is: test02,jpg', etc.) but the new image never pops up!
Does anyone have any explanation for why the image doesn't pop up?
Thanks!
nathan lachenmyer
Probably what is happening is that the image is getting destroyed by the garbage collector since the only reference to the image is a local variable.
Try keeping a permanent reference to the image, for example:
...
self.image_tk = ImageTk.PhotoImage(image)
...

Categories

Resources