Giuseppe Marziano
Giuseppe Marziano

Reputation: 69

How to display SVG on tkinter?

So I've done the hardest part of actually displaying the SVG with the help of Giovanni Gatto(credit: https://pythonprogramming.altervista.org/tkinter-shows-an-svg-file/)

I've modified the code to fit my needs, but after implementation, rather than it assigning the SVG to each button individually, it will only display it on the last button on the frame.

I can create a function for each button, but that would be inefficient, what is the best way to approach this?

Import

from PIL import Image, ImageTk
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
from io import BytesIO
import PIL.Image

Function This is within a class, but for readability convenience here is the function that converts converts.

def imgToButton(image):
   global img
   svgfile = svg2rlg(image)
   bytespng = BytesIO()
   renderPM.drawToFile(svgfile, bytespng, bg=0x393A4C, fmt="PNG")
   img = PIL.Image.open(bytespng)
   img = img.resize((20, 20), PIL.Image.ANTIALIAS)
   img.image = img
   img = ImageTk.PhotoImage(img)
   return img

Object

self.button1 = tk.Button(self.labelframe1)
self.button1.configure(activebackground='#393a4c', activeforeground='#000000', background='#393a4c', font='{Century Gothic} 13 {bold}')
self.button1.configure(foreground='#000000', highlightbackground='#393a4c', highlightcolor='#393a4c', highlightthickness='100')
self.button1.configure(justify='center', text='')
self.button1.configure(image=imgToButton("/Users/giuseppemarziano/Desktop/Scripts/Personal Projects/Auto Click GM/ACGM 0.04/ACGM Images/plus.svg"))
self.button1.place(height='25', relx='0.0', rely='0.0', width='25', x='5', y='3')
self.button1.bind('<1>', self.addSeq, add='+')

Apologies for using the full path, I am currently on an M1 MacBook Pro, which is an absolute nightmare for Python or coding in general

Ideally, I would like for each button to call the function, asign the image to the button, then repeat the process, rather than not doing that

I have also used self.button1.update(), but that still yields the same result.

FYI: if you are struggling with low-res .PNG images on Tkinter and want to use .SVG for high-res smaller images, you can implement this code to display an SVG image on a button or any object, all you need to do is make sure you are able to import svglib and import reportlab, you should already have pillow, if not: https://pillow.readthedocs.io/en/stable/installation.html

Upvotes: 1

Views: 4306

Answers (1)

furas
furas

Reputation: 142651

I can't test it but problem can be bug in PhotoImage which removes it when it is assigned to local variable.

Normally you could assign `PhotoImage to global variable or to other class

photo = ImageTk.PhotoImage(img)
img.photo = photo

not img.image = PIL.Image.open(bytespng)

But you run it many times so you assign new image to the same variable so previous image is removed from variable and then bug in PhotoImage removes it from memory.

You should keep all PhotoImage in different variables or on list

all_photoimages = []   # global list 

def imgToButton(image):

   svgfile = svg2rlg(image)
   bytespng = BytesIO()
   renderPM.drawToFile(svgfile, bytespng, bg=0x393A4C, fmt="PNG")
   
   img = PIL.Image.open(bytespng)
   img = img.resize((20, 20), PIL.Image.ANTIALIAS)
   
   photo = ImageTk.PhotoImage(img)
   #img.photo = photo
   all_photoimages.append(photo)  # keep in global list
   
   return photo

EDIT:

As @TheLizzard noticed in comment you can also assign PhotoImage to Button and then you don't need list all_photoimages because every PhotoImage will be assigned to different Button

And this method seems cleaner - because imgToButton doesn't have to use external list.

  # get photoimage 
  img = imgToButton(...) 

  # use it with button
  self.button1.configure(image=img)

  # assign to button to resolve problem with bug
  self.button1.tk_img = img

# -----

def imgToButton(image):

   svgfile = svg2rlg(image)
   bytespng = BytesIO()
   renderPM.drawToFile(svgfile, bytespng, bg=0x393A4C, fmt="PNG")
   
   img = PIL.Image.open(bytespng)
   img = img.resize((20, 20), PIL.Image.ANTIALIAS)
   
   return ImageTk.PhotoImage(img)

Upvotes: 2

Related Questions