首页 > 解决方案 > python: tkinter 切换 svg 内容

问题描述

我有各种 SVG 图像,它们的文件名是统一的数字格式,例如1.svg, 2.svg. 我想根据图像内容重命名每个文件

我为我编写了一个 python tkinter 应用程序来执行此操作,它有一个上一个和下一个按钮以及一个文本区域,它允许我返回并转到上一个并编辑文件名。

目前,我通过将 svg 内容写入临时 png 文件来成功显示 svg 内容,然后显示 png 文件。但是,我在切换 svg 内容时遇到了麻烦。

请看下面的代码:

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

我想要做的是,每当我调用refresh_image()函数时,tkinter canvas 会根据at索引更改 svg 内容,将当前的 svg 文件重写为temp.png文件并显示 png。

但是,我发现它是有线的,如果我将refresh_image()函数的内部部分写入全局范围,它就可以工作,但如果它在函数范围内,它就会停止工作!

我上面提供的代码示例不起作用,它调用refresh_image()函数。以下是工作版本,我没有调用函数,而是在refresh_image()最后执行了一次代码块:

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

请遵循测试说明:

  1. 我已将代码和 3 个 svg 文件示例放在 github 中,运行以下命令

git clone https://github.com/Weilory/svg_renamer

  1. 如果您尚未安装软件包:
pip install svglib
pip install Pillow
pip install tkinter
pip install reportlib
pip install lxml
  1. 将终端直接指向包含 SVG 文件夹和 renamer.py 的目录,打开 renamer.py,directory变量分配给 SVG 文件夹的绝对路径

  2. 通过终端运行代码

python renamer.py

github 上的renamer.py是功能范围,您应该看到没有图像显示在 tkinter 画布中。

  1. 散列refresh_image()函数和调用部分,在文件末尾取消散列全局单个执行部分,再次运行。

python renamer.py

图像将显示。但如果不调用该函数,我将无法切换 svg 内容。

这个问题整天困扰着我,如果有人为我提供解决方案或建议,我将不胜感激。

标签: pythonsvgtkintercanvas

解决方案


经过这么多试验,问题出现在pimg = ImageTK.PhotoImage(img),由于某种原因,如果ImageTK.PhotoImage()在全局范围内没有返回值,则数据将被遗忘,因此,如果我们声明pimg为全局变量并在函数中赋值refresh_image(),它将起作用。

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

我不知道为什么会发生这种情况,但是在使用 Pillow 模块时要小心,因为某些函数,例如ImageTK需要为全局变量赋值,换句话说,如果类或函数仅在函数范围内启动或声明,数据会被遗忘

如果有人可以解释为什么会发生这种情况,我很乐意接受它作为答案。

谢谢你的时间。


推荐阅读