Resizing Canvas on Tkinter first run issue - python

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.

Related

How to insert an image using Canvas in Tkinter?

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!

Python use image as background (graphics.py)

So my professor is making us use Graphis.py(zelle) to make a GUI I've made all the buttons my issue is that the module doesn't have any functionality that allows a image to be the background only the color. Do you guys have any idea how I can modify it so I can set the background to a image? The setBackground method is the one I believe needs to be edited
class GraphWin(tk.Canvas):
"""A GraphWin is a toplevel window for displaying graphics."""
def __init__(self, title="Graphics Window",
width=200, height=200, autoflush=True):
master = tk.Toplevel(_root)
master.protocol("WM_DELETE_WINDOW", self.close)
tk.Canvas.__init__(self, master, width=width, height=height)
self.master.title(title)
self.pack()
master.resizable(0,0)
self.foreground = "black"
self.items = []
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self._onClick)
self.bind_all("<Key>", self._onKey)
self.height = height
self.width = width
self.autoflush = autoflush
self._mouseCallback = None
self.trans = None
self.closed = False
master.lift()
self.lastKey = ""
if autoflush: _root.update()
def __checkOpen(self):
if self.closed:
raise GraphicsError("window is closed")
def _onKey(self, evnt):
self.lastKey = evnt.keysym
def setBackground(self, color):
"""Set background color of the window"""
self.__checkOpen()
self.config(bg=color)
self.__autoflush()
If you store your image as a gif, for example, Python will usually be able to display it. Here are the steps. I will assume that you create a graphic window named "win".
First, save your image in a file such as TestPic.gif.
Second, import the image and assign it a name such as b:
b = Image(Point(100,100),"TestPic.gif")
The Point(100,100) is the point that the image will be centered on. Now you want to display it:
Image.draw(b,win)
That's pretty much it. You can manipulate the image of course by using standard Python Graphics commands, such as moving it around, etc.
For reference, look at the Graphics pdf at this link:
http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf
It spells everything out pretty well.

threading and tkinter

I'm making an image manipulation class to be used in an intro CS course, using PIL for image manipulation and Tkinter to show the picture. In order for the users to be able to see the picture while manipulating it, I have the graphics operations running on a separate thread, using code similar to this question. This seems to be working (i.e., nothing crashes), but I can't get the image to display -- Tk is starting up, but no window comes up. The code looks like this:
self.root = Tk.Toplevel()
self.frame = tk.Frame(self.root, width=self.image.size[0], height=self.image.size[1])
img = ImageTk.PhotoImage(self.image)
self.label = tk.Label(self.frame, image=img)
self.label.image = img
self.label.pack()
self.frame.pack()
tick()
self.root.mainloop()
The tick function is similar to the one in the linked question. I suspect that my problem is due a a misunderstanding of Tkinter, but I really have no idea.
Also, I can't seem to get the program to exit nicely -- even if I set daemon=True when I construct the Thread which this is running on, I still have to hit C-c when I'm finished. That looks kinda ugly, and I'd rather not bother the students with spurious error messages.
EDIT: Here's some more code.
class Picture():
##
# Constructor. Creates Picture either by passing in the path of an image file as param
# or by passing in a tuple in the format of (x, y) to indicate the size of a blank image.
# Example 1: Picture("image.jpg") constructs Picture with image.jpg
# Example 2: Picture((500, 500)) constructs Picture with a blank image of size 500*500
def __init__(self, param):
# Check if parameter is the right type, because we can't
# overload functions
if isinstance(param, tuple) and len(param) == 2:
self.image = Image.new('RGB', (param))
elif isinstance(param, str):
self.image = Image.open(param)
else:
raise TypeError('Parameter to Picture() should be a string or 2-tuple!')
# Default values for pen
self.pen_color = (0, 0, 0)
self.pen_position = (0, 0)
self.pen_width = 1.0
self.pen_rotation = 0
# Pixel data of the image
self.pixel = self.image.load()
# Draw object of the image
self.draw = ImageDraw.Draw(self.image)
# The main window, and associated widgets.
self.root = None
self.label = None
self.frame = None
# Threading support, so that we can show the image while
# continuing to draw on it.
self.request_queue = queue.Queue()
self.result_queue = queue.Queue()
self.thread = threading.Thread(target=self._thread_main)
self.thread.start()
def _thread_main(self):
"""
Runs the main Tkinter loop, as well as setting up all the
necessary GUI widgets and whatnot. By running Tkinter on
a separate thread, we can keep the picture displaying
even after the user's program is finished drawing on it.
"""
def tick():
"""
Called whenever Tk's main loop is idle. This lets us perform
drawing operations on the right thread.
"""
try:
f, args, kwargs = self.request_queue.get_nowait()
except queue.Empty:
pass
else:
value = f(*args, **kwargs)
self.result_queue.put(value)
self.root.after_idle(tick)
self.root = tk.Toplevel()
self.frame = tk.Frame(self.root, width=self.image.size[0], height=self.image.size[1])
img = ImageTk.PhotoImage(self.image)
self.label = tk.Label(self.frame, image=img)
# This line ensures that Python doesn't try to garbage collect
# our photo, due to a bug in Tk.
self.label.image = img
self.label.pack()
self.frame.pack()
tick()
self.root.mainloop()
def _submit_operation(self, f, *args, **kwargs):
"""
Submits an operation to the request queue. The arguments
should consist of a function, any positional arguments
to said function, and any keyword arguments to the function.
If f returns a value, that value will be returned.
Any function that does something with the picture (i.e.,
saving it, drawing to it, reading from it, etc.) should
be called only by submitting it to the queue.
"""
self.request_queue.put((f, args, kwargs))
return self.result_queue.get()
##
# Display the picture.
def display(self):
def display_func():
img = ImageTk.PhotoImage(self.image)
self.label.configure(image=img)
self.label.image = img
self.label.pack()
self.frame.pack()
self._submit_operation(display_func)

Python Tkinker - showing a jpg as a class method not working

I'm trying to show a jpg image as background for a GUI thing I'm building.
I can get it to work in a single method:
from Tkinter import *
from PIL import Image, ImageTk
class MakeGUI(object):
master = None
w = None
def __init__(self):
self.canvasSizeY = 400 #height
self.canvasSizeX = 640 #width
def setupCanvas(self):
"""
preps the canvas for drawing.
"""
self.master = Tk()
self.w = Canvas(self.master, width=self.canvasSizeX, height=self.canvasSizeY)
self.w.config(bg='white')
image = Image.open("background.jpg")
photo = ImageTk.PhotoImage(image)
self.w.create_image(0,0, image=photo, anchor=NW)
self.w.pack()
mainloop()
def main():
makeGUI = MakeGUI()
makeGUI.setupCanvas()
if __name__ == '__main__':
main()
But when I try and make the canvas in one method, and show the canvas in another, it doesn't show the jpg (when I've been testing, I've created and shown & text and rectangles using this approach):
from Tkinter import *
from PIL import Image, ImageTk
class MakeGUI(object):
master = None
w = None
def __init__(self):
self.canvasSizeY = 400 #height
self.canvasSizeX = 640 #width
def setupCanvas(self):
"""
preps the canvas for drawing.
"""
self.master = Tk()
self.w = Canvas(self.master, width=self.canvasSizeX, height=self.canvasSizeY)
self.w.config(bg='white')
image = Image.open("background.jpg")
photo = ImageTk.PhotoImage(image)
self.w.create_image(0,0, image=photo, anchor=NW)
def showImage(self):
"""
pushes the image to the screen
"""
self.w.pack()
self.w.mainloop()
def main():
makeGUI = MakeGUI()
makeGUI.setupCanvas()
if __name__ == '__main__':
main()
I want to use the GUI dynamically to show some text as I work through some editing, so I'm interested to understand what I've got wrong before I get too far into the build in case its a showstopper...
The most obvious problem is that in the second case you are never calling showImage. Even after you do call that function, your image probably won't show up. Images will be garbage-collected if there isn't a reference to them. It may seem like there's a reference because you're adding it to a canvas, but that isn't enough.
You'll need to do something like:
self.photo = ImageTk.PhotoImage(image)
Finally, I recommend that you take the call to mainloop out of showImage. mainloop must always be called exactly once, so most typically it is the last line of code in your program, or the last line of code in your main function.
A more common way to make a Tkinter application is to subclass either the Tk object or a Frame object, rather than having your main application be a generic object. For example:
class MyApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
...
self.setupCanvas(...)
...
if __name__ == "__main__":
app = MyApp()
app.mainloop()

Tkinter custom window

I want to make a window in Tk that has a custom titlebar and frame. I have seen many questions on this website dealing with this, but what I'm looking for is to actually render the frame using a canvas, and then to add the contents to the canvas. I cannot use a frame to do this, as the border is gradiented.
According to this website: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_window-method, I cannot put any other canvas items on top of a widget (using the create_window method), but I need to do so, as some of my widgets are rendered using a canvas.
Any suggestions on how to do this? I'm clueless here.
EDIT: Bryan Oakley confirmed that rendering with a canvas would be impossible. Would it then be possible to have a frame with a custom border color? And if so, could someone give a quick example? I'm sort of new with python.
You can use the canvas as if it were a frame in order to draw your own window borders. Like you said, however, you cannot draw canvas items on top of widgets embedded in a canvas; widgets always have the highest stacking order. There is no way around that, though it's not clear if you really need to do that or not.
Here's a quick and dirty example to show how to create a window with a gradient for a custom border. To keep the example short I didn't add any code to allow you to move or resize the window. Also, it uses a fixed color for the gradient.
import Tkinter as tk
class GradientFrame(tk.Canvas):
'''A gradient frame which uses a canvas to draw the background'''
def __init__(self, parent, borderwidth=1, relief="sunken"):
tk.Canvas.__init__(self, parent, borderwidth=borderwidth, relief=relief)
self._color1 = "red"
self._color2 = "black"
self.bind("<Configure>", self._draw_gradient)
def _draw_gradient(self, event=None):
'''Draw the gradient'''
self.delete("gradient")
width = self.winfo_width()
height = self.winfo_height()
limit = width
(r1,g1,b1) = self.winfo_rgb(self._color1)
(r2,g2,b2) = self.winfo_rgb(self._color2)
r_ratio = float(r2-r1) / limit
g_ratio = float(g2-g1) / limit
b_ratio = float(b2-b1) / limit
for i in range(limit):
nr = int(r1 + (r_ratio * i))
ng = int(g1 + (g_ratio * i))
nb = int(b1 + (b_ratio * i))
color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
self.create_line(i,0,i,height, tags=("gradient",), fill=color)
self.lower("gradient")
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.wm_overrideredirect(True)
gradient_frame = GradientFrame(self)
gradient_frame.pack(side="top", fill="both", expand=True)
inner_frame = tk.Frame(gradient_frame)
inner_frame.pack(side="top", fill="both", expand=True, padx=8, pady=(16,8))
b1 = tk.Button(inner_frame, text="Close",command=self.destroy)
t1 = tk.Text(inner_frame, width=40, height=10)
b1.pack(side="top")
t1.pack(side="top", fill="both", expand=True)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Here is a rough example where the frame, titlebar and close button are made with canvas rectangles:
import Tkinter as tk
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# Get rid of the os' titlebar and frame
self.overrideredirect(True)
self.mCan = tk.Canvas(self, height=768, width=768)
self.mCan.pack()
# Frame and close button
self.lFrame = self.mCan.create_rectangle(0,0,9,769,
outline='lightgrey', fill='lightgrey')
self.rFrame = self.mCan.create_rectangle(760,0,769,769,
outline='lightgrey', fill='lightgrey')
self.bFrame = self.mCan.create_rectangle(0,760,769,769,
outline='lightgrey', fill='lightgrey')
self.titleBar = self.mCan.create_rectangle(0,0,769,20,
outline='lightgrey', fill='lightgrey')
self.closeButton = self.mCan.create_rectangle(750,4,760, 18,
activefill='red', fill='darkgrey')
# Binds
self.bind('<1>', self.left_mouse)
self.bind('<Escape>', self.close_win)
# Center the window
self.update_idletasks()
xp = (self.winfo_screenwidth() / 2) - (self.winfo_width() / 2)
yp = (self.winfo_screenheight() / 2) - (self.winfo_height() / 2)
self.geometry('{0}x{1}+{2}+{3}'.format(self.winfo_width(),
self.winfo_height(),
xp, yp))
def left_mouse(self, event=None):
obj = self.mCan.find_closest(event.x,event.y)
if obj[0] == self.closeButton:
self.destroy()
def close_win(self, event=None):
self.destroy()
app = Application()
app.mainloop()
If I were going to make a custom GUI frame I would consider creating it with images,
made with a program like Photoshop, instead of rendering canvas objects.
Images can be placed on a canvas like this:
self.ti = tk.PhotoImage(file='test.gif')
self.aImage = mCanvas.create_image(0,0, image=self.ti,anchor='nw')
More info →here←

Categories

Resources