python: tkinter переключает содержимое svg

#python #svg #tkinter #canvas

#python #svg #tkinter #холст

Вопрос:

У меня есть множество изображений SVG, их имена файлов представлены в едином числовом формате, например 1.svg , 2.svg . Я хочу переименовать каждый файл на основе содержимого изображения.

Для этого я закодировал приложение python tkinter, в котором есть кнопки previous и next и текстовая область, что позволяет мне вернуться назад и перейти к предыдущему и отредактировать имя файла.

В настоящее время мне удается отображать содержимое svg, записывая содержимое svg во временный файл png, а затем отображая файл 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 изменяет содержимое svg на основе at индекса, который перезаписывает текущий файл svg в temp.png файл и отображает png.

Тем не менее, я нахожу это подключенным, это работает, если я записываю внутреннюю часть refresh_image() функции в глобальную область, но если она находится в функциональной области, она перестает работать!

приведенный выше пример кода, который он вызывает function, не работает 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

renamer.py на github — это функциональная область, в которой вы не должны видеть изображения, отображаемые в tkinter canvas.

  1. хэшируйте refresh_image() функцию и вызывающую часть, отключите глобальную единую исполняемую часть в конце файла, запустите снова.

python renamer.py

появится изображение. но без вызова функции я не смогу переключать содержимое svg.

Эта проблема раздражала меня весь день, я буду очень признателен, если кто-нибудь предложит мне решение или какие-то предложения.

Комментарии:

1. если lxml cannot import etree при запуске появляется ошибка renamer.py , просто переустановите ее: pip uninstall lxml , pip install lxml

2. Я уверен, что в функциональной области ошибка скрывается в этой строке pimg = ImageTk.PhotoImage(img)

Ответ №1:

после стольких попыток возникает проблема at 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 , требуют присвоения значения глобальной переменной, другими словами, если класс или функция инициированы или объявлены только в функциональной области, данные будут забыты

если кто-нибудь может объяснить, почему это происходит, я бы с удовольствием принял это как ответ.

Спасибо за ваше время.

Комментарии:

1. Не забывайте , что это сбор мусора, потому что интерпретатор считает, что никто все еще не ссылается на него после функции.