python: tkinter switch svg content - python

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.

Related

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()

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.

PIL: Jump to next photo after one click event

I am preparing data for deep running. So I have to get certain pixel coordinates for each picture. Only one coordinate per photo is required.
So when I use PIL to input one click, I try to implement coordinates so that I can go to the next picture.
However, when I write the code as below, the coordinates are output in duplicate to only one picture, and the next picture does not appear on the screen.
How can I make sure that only one coordinate is recorded on a single picture?
from PIL import Image, ImageTk
import tkinter
import os
URL = './SavedImage/'
imgList = os.listdir(URL)
print(imgList)
width = 852
height = 480
stepW = 852/4
stepH = 480/5
def callback(event):
print("clicked at: ", event.x, event.y)
window = tkinter.Tk(className='pla')
for file in sorted(imgList):
a=True
image = Image.open(os.path.join(URL, file))
print(image)
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)
canvas.bind("<Button-1>", callback)
tkinter.mainloop()
I am not 100% sure I understand what you need but it looks like to me you are trying to get one set of cord's for each image in a list of images.
I would do this by creating a function and a tracking variable to loop through each image on at a time and on click update a new list with the image and cord's then loop to next image.
Let me know if you have any questions.
Example:
from PIL import Image, ImageTk
import tkinter
import os
URL = './SavedImage/'
imgList = os.listdir(URL)
width = 852
height = 480
stepW = 852/4
stepH = 480/5
tracker = 0
list_images_with_cords = [] # added list for final results
def callback(event):
# Added global's.
global tracker,list_images_with_cords
# Used to append final results to list.
list_images_with_cords.append([imgList[tracker], event.x, event.y])
# This tracker lets us go through each item on the list.
tracker += 1
# After appending list go to next image.
open_next()
window = tkinter.Tk(className='pla')
# Creates just one canvas that we can update later.
canvas = tkinter.Canvas(window)
canvas.pack()
def open_next():
# Adding global's.
global image, canvas, image_tk, tracker
# Clearing canvas before drawing new image.
canvas.delete("all")
# Checking for valid index in list.
if tracker < len(imgList):
image = Image.open(os.path.join(URL, imgList[tracker]))
# use config() to update canvas.
canvas.config(width=image.size[0], height=image.size[1])
image_tk = ImageTk.PhotoImage(image)
canvas.create_image(image.size[0]//2, image.size[1]//2, image=image_tk)
canvas.bind("<Button-1>", callback)
else:
# This else statement is just for when we run out of images.
# It will display all the results in a textbox.
canvas.destroy()
txt = tkinter.Text(window, width=25)
txt.pack()
for item in list_images_with_cords:
txt.insert("end", "{}\n\n".format(item))
open_next()
tkinter.mainloop()

Display an image with Tkinter

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).

Tkinter images crashing

So I made a script in python with Tkinter and the thing is that the first Tkinter window pops up without problems but when the code goes to the second window it says :
_tkinter.TclError: image "pyimage1" doesn't exist
and I didn't find anything that helped me, could someone help me please ?
Here is the code :
from Tkinter import *
from PIL import ImageTk, Image
def choose():
global name, chosen
name = name1.get()
chosen = chosen1.get()
print name
print chosen
root0.quit()
root0 = Tk()
name1 = Entry(root0)
name1.pack()
chosen1 = Entry(root0)
chosen1.pack()
Button(root0, text="ENTER", command=choose).pack()
root0.mainloop()
root = Tk()
img = ImageTk.PhotoImage(Image.open('person1.png'))
panel1 = Label(root, image = img)
panel1.pack(side="left")
img2 = ImageTk.PhotoImage(Image.open('person2.png'))
panel2 = Label(root, image = img2)
panel2.pack(side="right")
root.mainloop()
by the way, the python version is 2.7
This is a side effect of using 2 roots (Tk() instances). The images default to associate with the first root window. The quick fix is to provide the image with the correct root:
img2 = ImageTk.PhotoImage(Image.open('person2.png'), master=root)
The proper fix is to never use more than one Tk(). Put all your code into Frame instances, and then destroy one and load the other when the time is right:
import Tkinter as tk
def choose():
global name, chosen
name = name1.get()
chosen = chosen1.get()
print name
print chosen
frame0.destroy() # kill this frame
frame1.pack() # open new frame
root = tk.Tk()
frame0 = tk.Frame(root)
name1 = tk.Entry(frame0)
name1.pack()
chosen1 = tk.Entry(frame0)
chosen1.pack()
tk.Button(frame0, text="ENTER", command=choose).pack()
frame1 = tk.Frame(root)
img = ImageTk.PhotoImage(Image.open('person1.png'))
panel1 = tk.Label(frame1, image = img)
panel1.pack(side="left")
img2 = ImageTk.PhotoImage(Image.open('person2.png'))
panel2 = tk.Label(frame1, image = img2)
panel2.pack(side="right")
#start the program
frame0.pack() # load frame0
root.mainloop()
Note I also moved you away from the evil wildcard imports (from module import *).

Categories

Resources