I am trying to insert an image in my python application using Canvas in tkinter. The code for the same is:
class Welcomepage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
canvas = tk.Canvas(self, width = 1000, height = 1000, bg = 'blue')
canvas.pack(expand = tk.YES, fill = tk.BOTH)
image = tk.PhotoImage(file="ice_mix.gif")
canvas.create_image(480, 258, image = image, anchor = tk.NW)
The image is getting read from the source but still not getting displayed in the frame.I am new to the GUI programming someone please help me out.
The likely issue here is that the image is being garbage collected by Python and therefore not being displayed - which is what #nae's comment is suggesting. Attaching it to the self reference will stop it from being garbage collected.
self.image = tk.PhotoImage(file="ice_mix.gif") # Use self.image
canvas.create_image(480, 258, image = self.image, anchor = tk.NW)
The Tkinter Book on effbot.org explains this:
Note: When a PhotoImage object is garbage-collected by Python (e.g.
when you return from a function which stored an image in a local
variable), the image is cleared even if it’s being displayed by a
Tkinter widget.
To avoid this, the program must keep an extra reference to the image
object. A simple way to do this is to assign the image to a widget
attribute, like this:
label = Label(image=photo)
label.image = photo # keep a reference!
label.pack()
If You still need to use a Canvas instead a Label for image placement inside a function or a method You can use an external link for image, and use a global specificator for this link inside a function.
You may need to use SE anchor, not NW.
This code works successfully (gets an OpenCV image from USB-camera and place it in a Tkinter Canvas):
def singleFrame1():
global imageTK # declared previously in global area
global videoPanel1 # also global declaration (initialized as "None")
videoCapture=cv2.VideoCapture(0)
success,frame=videoCapture.read()
videoCapture.release()
vHeight=frame.shape[0]
vWidth=frame.shape[1]
imageRGB=cv2.cvtColor(frame,cv2.COLOR_BGR2RGB) # OpenCV RGB-image
imagePIL=Image.fromarray(imageRGB) # PIL image
imageTK=ImageTk.PhotoImage(imagePIL) # Tkinter PhotoImage
if videoPanel1 is None:
videoPanel1=Canvas(root,height=vHeight,width=vWidth) # root - a main Tkinter object
videoPanel1.create_image(vWidth,vHeight,image=imageTK,anchor=SE)
videoPanel1.pack()
else:
videoPanel1.create_image(vWidth,vHeight,image=imageTK,anchor=SE)
About
Seems like a program that showcase a GIF. I think it is better to "fix" the code for you including step by step tutorial since you look like a beginner.
Fix
Okay, so here is your code.
class Welcomepage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
canvas = tk.Canvas(self, width = 1000, height = 1000, bg = 'blue')
canvas.pack(expand = tk.YES, fill = tk.BOTH)
image = tk.PhotoImage(file="ice_mix.gif")
canvas.create_image(480, 258, image = image, anchor = tk.NW)
First of all, you never noticed the fact that you didn't add pass to the first class.
class Welcomepage(tk.Frame):
pass
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
canvas = tk.Canvas(self, width = 1000, height = 1000, bg = 'blue')
canvas.pack(expand = tk.YES, fill = tk.BOTH)
image = tk.PhotoImage(file="ice_mix.gif")
canvas.create_image(480, 258, image = image, anchor = tk.NW)
The second fact is that since you used the double quote you should be keep using them, because then you can be a great developer. I experienced such a mess as a Python stack dev :(
class Welcomepage(tk.Frame):
pass
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
canvas = tk.Canvas(self, width = 1000, height = 1000, bg = "blue")
canvas.pack(expand = tk.YES, fill = tk.BOTH)
image = tk.PhotoImage(file="ice_mix.gif")
canvas.create_image(480, 258, image = image, anchor = tk.NW)
And try using the following code for your line 8~9.
self.image = tk.PhotoImage(file="ice_mix.gif")
canvas.create_image(480, 258, image = self.image, anchor = tk.NW)
I've also noticed that instead of your space, it is supposed to be this.
class Welcomepage(tk.Frame):
pass
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
canvas = tk.Canvas(self, width = 1000, height = 1000, bg = "blue")
canvas.pack(expand = tk.YES, fill = tk.BOTH)
self.image = tk.PhotoImage(file="ice_mix.gif")
canvas.create_image(480, 258, image = image, anchor = tk.NW)
Hope you enjoyed and have a nice day!
Related
I want to make a program that begins as a small window, then when given a path to an image, it maximises the screen and places the image in the centre.
If you run the code below you will see that the window maximises, the image is loaded into memory, the code runs with no errors and self.open_image calls self.draw_image(self.pimg) which runs without error, however the image is not present on the canvas.
If I click the button "Fix" and call self.fix it calls self.draw_image(self.pimg) which runs without error and correctly draws the image.
How can you call the same function twice with the same arguments and get different results. What is different.
I get the feeling this is happening because something has taken place in the main loop that hasn't taken place at the end of self.__init__, so that when i call self.draw_image the second time self.cv.create_image is able to interact with something in the resizable canvas.
In this example I am happy to assume the program will always begin as a small window and become a maximised window untill it is closed, never being resized again, however in my real program I would like to make it more dynamic where the window handles resizing sensibly, this is just a minimum reproducible example. It is for this reason that I would like to use the ResizingCanvas class (or one like it) even though I feel that it is likely the cause of the issue I am experiencing.
I have tried using breakpoints and stepping through the code watching the variables get created but I cant see the difference between the self.cv the first time around and self.cv after I click the button.
I read about a similar issue here on this question and he suggests binding "<Configure>" To the canvas and passing the coords from the event to the canvas. However this has already been implemented in ResizingCanvas
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.root = Tk()
self.name = name # Filename
myframe = Frame(self.root)
myframe.pack(fill=BOTH, expand=YES)
self.cv = ResizingCanvas(myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
self.b = Button(self.cv, text = 'Fix', command = self.fix).grid(row=1,column=1)
self.open_img()
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.root.state("zoomed")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.root.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
What should happen in the video:
I click run and the image is displayed immediately, without having to click the fix button.
What does happen in the video:
I click run and the image is not displayed until I click the fix button, afterwhich it works.
Changing
self.root.state("zoomed") to self.root.state("normal")
in your code (I am working on Python3) I can only get:
[
the image above, played a little bit starting from How to get tkinter canvas to dynamically resize to window width?
and now the code seems to work with me:
from time import sleep
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent, **kwargs):
# Canvas.__init__(self,parent,**kwargs)
print(kwargs)
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
# self.height = self.winfo_reqheight()
# self.width = self.winfo_reqwidth()
self.height = self.winfo_height()
self.width = self.winfo_width()
# self.height = height
# self.width = width
# self.__dict__.update(kwargs)
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = (event.width)//self.width
hscale = (event.height)//self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.pippo = Tk()
self.name = name # Filename
self.myframe = Frame(self.pippo)
self.myframe.pack(side = BOTTOM, expand=YES)
# myframe.pack(fill=BOTH, expand='TRUE')
self.cv = ResizingCanvas(self.myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
# sleep(2)
self.b = Button(self.myframe, text = 'Fix', command = self.fix)#.grid(row=1,column=1)
self.b.pack(side=TOP)
self.open_img()
# self.pippo.mainloop() ## use it if you eliminate def run
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
# self.cv.create_image(self.pippo.winfo_screenwidth()/2,
# self.pippo.winfo_screenheight()/2, image=self.img, tags=('all'))
self.cv.create_image(self.pippo.winfo_width()/2,
self.pippo.winfo_reqheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.pippo.state("normal")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.pippo.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
don't know about your question though, but wanted to be sure your starting example works right. Let me know if it could be related to python/pillow/tkinter version or something else
Here my window image results before ad after pressing fix button :
At the end found out that your code does work as long as you use
self.root.attributes('-zoomed', True) instead of `self.root.state("zoomed")`
The problem is here. self.root.winfo_screenwidth()
Change it to self.cv.width. I don't know why.
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
Change the last line to
self.cv.create_image(self.cv.width/2,
self.cv.height/2, image=self.img, tags=('all'))
Fixes the issue.
Tk.winfo_screenwidth() according to https://tkdocs.com/shipman/universal.html returns the width of the screen, indepedant of the size of the window, so even if you have a small window on a 1920x1080 display, this function will return 1920.
self.cv.width returns the width of the canvas object.
I am new to Tkinter and python. I am trying to upload two images and then perform some operations on them. The problem is that the Window class is loading all at once or the code in running parallel, so the images uploaded after that have been already assigned to None since they were uploaded later in the ScrollableFrame class and did not have a value earlier.
from tkinter import *
from tkinter import filedialog
from PIL import Image, ImageTk, ImageOps
import os
# ************************
# Scrollable Frame Class
# ************************
row=0
column=0
imagePaths = []
#Class to generate a frame to add to the GUI with vertical and horizontal scroll bars
class ScrollableFrame(Frame):
#The Constructor method for the class
def __init__(self, parent , *args, **kw):
Frame.__init__(self, parent, *args, **kw)
#Defining the position of the frame grid
self.grid(row = row , column = column)
self.image = None
self.imageFile = None
#Defining the vertical scroll bar
vscrollbar = Scrollbar(self, orient=VERTICAL)
vscrollbar.grid(row=row, column=column+1, sticky=N+S)
#Defining the horizontal scroll bar
hscrollbar = Scrollbar(self, orient = 'horizontal')
hscrollbar.grid(row=row+1, column=column, sticky=E+W)
#Defining the canvas to put the scroll bars on
canvas = Canvas(self, bd=0, highlightthickness=0, yscrollcommand=vscrollbar.set, xscrollcommand=hscrollbar.set)
canvas.grid(row=row, column=column, sticky = N+S+E+W)
canvas.config( width=800, height = 800 )
#Defining the scrolling commands (vertically and horizontally )
vscrollbar.config(command=canvas.yview)
hscrollbar.config(command=canvas.xview)
#Defining the scroll region where the scrolling is active
canvas.config(scrollregion= (0,0,1280,1024))
self.canvas = canvas
def openImage(self):
#Getting the path of the image
imageFile = filedialog.askopenfilename(initialdir=os.getcwd(),title="Select BMP File",filetypes=[("BMP Files",("*.bmp",".png",".jpg",".jpeg",".tif",".tiff"))])
#Assigning the image value to this frame object
self.imageFile = imageFile
if not imageFile:
return
def showImage(self):
#Getting the path of the image
imageFile = filedialog.askopenfilename(initialdir=os.getcwd(),title="Select BMP File",filetypes=[("BMP Files",("*.bmp",".png",".jpg",".jpeg",".tif",".tiff"))])
#Assigning the image value to this frame object
self.imageFile = imageFile
if not imageFile:
return
#Checking for the extension of the image
filename, file_extension = os.path.splitext(imageFile)
#If it is a .bmp, this means that it is an HD image, where we can directly display it
if file_extension == '.bmp':
imageToDisplay = Image.open(imageFile)
#border = (0, 0, 0, 66) #Decide on the area you want to crop in terms of no. pixels: left, up, right, bottom
#ImageOps.crop(imageToDisplay, border)
img = ImageTk.PhotoImage(imageToDisplay)
self.image = img
#print ("Done conversion")
self.canvas.create_image(row, column, image=self.image, anchor=NW)
class Window(Frame):
def __init__(self, master=None):
global row, column,imagePaths
Frame.__init__(self, master)
self.master = master
self.pos = []
self.master.title("BMP Image GUI")
self.pack(fill=BOTH, expand=1)
self.label = Label(self, text="Instructions: \n 1. Open the HD image. \n 2. Open the EBSD image. \n 3. Open the Color Map image.", anchor=W, justify=LEFT)
self.label.place(x=1640, y=0)
menu = Menu(self.master)
self.master.config(menu=menu)
self.frame1 = ScrollableFrame(self)
row=0
column=1
self.frame2 = ScrollableFrame(self)
# File Bar
file = Menu(menu)
file.add_command(label="Open HD image", command=self.frame1.showImage)
img = Image.open("original.bmp")
HD = self.frame2.imageFile
file.add_command(label="Open EBSD image", command=self.frame2.openImage)
EBSD = self.frame2.imageFile
print (HD)
print (EBSD)
root = tk.Tk()
root.geometry("%dx%d" % (1670, 1024))
root.title("BMP Image GUI")
app = Window(root)
app.pack(fill=tk.BOTH, expand=1)
#print (HD)
root.mainloop()
So printing the HD and EBSD images is giving None. What I am aiming to to make them get the actual value assigned after the upload.
This is a lot of code and it doesn't run. It also has a few problems. When you are coding complex applications it is best to do it one little piece at a time or you'll have problems finding the problems. Here are a few:
Don't use global variables in an object oriented application. The names row, column and imagePaths should belong to either of the two classes.
The menu doesn't work because you have not implemented it correctly:
file = Menu(menu)
menu.add_cascade(label='File', menu=file) # You need this for it to work
file.add_command(label="Open HD image", command=self.frame1.showImage)
# etc...
You are packing app twice, once in it's __init__() function and once after it's been created (in the global scope).
The scrollable frames are packed in front of the Label with instructions so you can't see it.
Try fixing these problems by writing components, and when each component works then combine them. If there is a problem with any of the components, or if everything works but for one thing, come back here and we will be able to give you a better answer.
This question already has answers here:
Why does Tkinter image not show up if created in a function?
(5 answers)
Closed 6 years ago.
i'm making a monopoly game, & i am trying to draw image on canvas, but it will only work if not in function:
def make_image(root, location, canvas):
photo = PhotoImage(file = root)
canvas.create_image(location["X"],location["Y"], image = photo, anchor = "nw")
class something():
def start(self, controller):
self.controller = controller
#photo = PhotoImage(file = "googolopoly.png")
#self.canvas.create_image(0,0, image = photo, anchor = "nw")
make_image("googolopoly.png", {"X":0,"Y":0}, self.canvas)
make_text(self.canvas, "MONOPOLY!!!!", {"X":1050,"Y":20})
make_button(self.main_tk, self.canvas, "roll dice", lambda: self.roll_dice(), {"X":1100, "Y":50}, 100)
for i in range(controller.player_number):
self.players.append(make_text(self.canvas, str(i+1), {"X":902+i*10, "Y":946}))
self.main_tk.mainloop()
currently it won't draw a picture, but if i get down the comments it will work (no function)
it also happens after main loop, when i want to draw players
i really need it as a function. what to do? if you need i can put some more code
You were missing this line of code: myCanvas.image = photo.
And even if it would be easier to draw the image on a Label, with this code you can do it on a Canvas with the function make_image():
from Tkinter import *
def make_image(filename, location, canvas):
photo = PhotoImage(file=filename)
myCanvas.image = photo
myCanvas.create_image(0,0, image = photo, anchor = "nw")
root = Tk()
myCanvas = Canvas(root, width=100, height=100)
myCanvas.grid()
make_image("image.gif", (5,5,95,95), myCanvas)
root.mainloop()
I have created a short script to display a photo in a Tkinter canvas element. I size the canvas element to have the same size as the photo, but when I launch the window, I generally only see a small portion of the image, no matter how much I expand the window.
Also when I print out the size of the image, which I am using to set the size of the canvas, this size says (922, 614) whereas when I record mouse clicks on the canvas, the lower-right hand corner is at something like (500, 300). My code is below. What should I change so that the canvas is the same size as the image and fully shows the image?
class AppWindow(Frame):
def __init__(self, parent, list_of_files, write_file):
Frame.__init__(self, parent)
self.parent = parent
...
self.image = None
self.canvas = None
self.index = -1
self.loadImage()
self.initUI()
self.resetCanvas()
def initUI(self):
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
...
self.canvas = Tkinter.Canvas(self, width = self.image.width(), height = self.image.height())
def loadImage(self):
self.index += 1
img = cv2.imread(self.list_of_files[self.index])
img = cv2.resize(img, (0,0), fx = IMAGE_RESIZE_FACTOR, fy = IMAGE_RESIZE_FACTOR)
b, g, r = cv2.split(img)
img = cv2.merge((r,g,b))
im = Image.fromarray(img)
self.image = ImageTk.PhotoImage(image=im)
def resetCanvas(self):
self.canvas.create_image(0, 0, image=self.image)
self.canvas.place(x = 0, y = 0, height = self.image.height(), width = self.image.width())
Here is a screenshot showing a photo and how it is presented in the Tkinter canvas:
Here's what I have tried so far:
Not resizing the image or changing the resizing amount - this doesn't do anything
Making the canvas double the size of the image rather than equal to the image, likes so self.canvas = Tkinter.Canvas(self, width = self.image.width()*2, height = self.image.height()*2) This does make the canvas larger (I can tell because I have some drawing functions on the canvas), but the image stays the same small size, not showing the entire image
When I display the cv2 format image with cv2.imshow() I see the full image, so it's not cv2 that is cutting off portions of the image.
I realized what I have above is not a complete working example. I've pared the script down, and now running this I still see the same problem:
import Tkinter
import Image, ImageTk
from Tkinter import Tk, BOTH
from ttk import Frame, Button, Style
import cv2
import os
import time
import itertools
IMAGE_RESIZE_FACTOR = .3
class AppWindow(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.loadImage()
self.initUI()
def loadImage(self):
img = cv2.imread("w_4131.jpg")
img = cv2.resize(img, (0,0), fx = IMAGE_RESIZE_FACTOR, fy = IMAGE_RESIZE_FACTOR)
b, g, r = cv2.split(img)
img = cv2.merge((r,g,b))
im = Image.fromarray(img)
self.image = ImageTk.PhotoImage(image=im)
def initUI(self):
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
print "width and height of image should be ", self.image.width(), self.image.height()
self.canvas = Tkinter.Canvas(self, width = self.image.width(), height = self.image.height())
self.canvas.pack()
self.canvas.create_image(0, 0, image=self.image)
def main():
root = Tk()
root.geometry("250x150+300+300")
app = AppWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
When you place an image on a canvas at 0,0, that specifies the location of the center of the image, not the upper-left corner. The image is all there, it's just that you're only seeing the bottom-right corner.
Add anchor="nw" when creating the image for the coordinates to represent the upper-left corner of the image:
self.canvas.create_image(0, 0, image=self.image, anchor="nw")
As for the canvas not being the same size of the image, I'm not seeing that. The image seems to fit the canvas perfectly.
I'm making some pretty pictures using a tkinter canvas and overlaying text on top of circles like in the following picture:
http://static.guim.co.uk/sys-images/Guardian/Pix/pictures/2012/11/6/1352220546059/Causes-of-deaths-graphic-008.jpg
I want the font size to be dependent on the same number that the circle size is dependent on.
tempfont = tkFont.Font(family='Helvetica',size=int(round(ms*topnode[1])))
self.display.create_text(center[0],center[1],fill = "#FFFFFF",text = int(round(ms*topnode[1])),font = tempfont)
My problem is that when I use the above code, the overlayed text is a constant size for every text object. The text itself is right, as in it displays the number that I want the font size to be, just not in the correct font size. I've experimented with putting in constant integers in the size definition (works as it's supposed to), and adding a del(tempfont) immediately after the above 2 lines of code, but I haven't found what fixes this problem yet.
What am I doing wrong?
Here's a self-contained little program that reproduces the problem:
from Tkinter import *
import tkFont
class TestApp(Frame):
def __init__(self, master=None, height = 160, width = 400):
Frame.__init__(self, master)
self.grid()
self.createWidgets()
def createWidgets(self):
self.display = Canvas(self, width = 800, height = 320, bg = "#FFFFFF")
self.display.grid(row=0,column=0)
def recurtext(tsize):
if tsize > 20:
recurtext(tsize-10)
tempfont = tkFont.Font(family='Helvetica',size=tsize)
self.display.create_text(800 - (tsize*12),160, text = str(tsize), font = tempfont)
recurtext(60)
app = TestApp()
app.master.title("Test")
app.mainloop()
The gist is that recurtext resizes the font recursively, and shows writes out the font size in that size... or I think it should. Maybe this is a bug with tkinter, but I'm still holding on to some hope that I'm the one who made a mistake in the logic here.
I've never run across this behavior before; it looks like a Tkinter bug. The good news is, there appears to be a workaround. If you give each font a unique name the problem seems to vanish.
The following example shows multiple lines, each with a different font size:
import Tkinter as tk
import tkFont
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.display = tk.Canvas(self, width=400, height=600, background="black")
self.display.pack(side="top", fill="both", expand=True)
y = 10
for size in range (2, 38, 2):
tempfont = tkFont.Font(family='Helvetica',size=size,
name="font%s" % size)
self.display.create_text(10, y, fill = "#FFFFFF",text = size,
font = tempfont, anchor="nw")
y = y + tempfont.metrics()["linespace"]
if __name__ == "__main__":
root = tk.Tk()
frame = Example(parent=root)
frame.pack(side="top", fill="both", expand=True)
root.mainloop()