I am new to Python GUI development with Tkinter.
I am trying to make the app window a certain size and I want the webcam view in the window to fill the size of the main window.
When I set the window size, the webcam does not fill the entire window.
How can I make the elements fill the entire window when I customize the size of the window?
Here is my code:
import tkinter as tk
from PIL import Image, ImageTk
import cv2
class MainWindow():
def __init__(self, window, cap):
self.window = window
self.cap = cap
self.width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.interval = 20 # Interval in ms to get the latest frame
# Create canvas for image
self.canvas = tk.Canvas(self.window, width=self.width, height=self.height)
self.canvas.grid(row=0, column=0)
self.canvas.pack(fill="both", expand=True)
# Update image on canvas
self.update_image()
def update_image(self):
# Get the latest frame and convert image format
self.image = cv2.cvtColor(self.cap.read()[1], cv2.COLOR_BGR2RGB) # to RGB
self.image = Image.fromarray(self.image) # to PIL format
self.image = ImageTk.PhotoImage(self.image) # to ImageTk format
# Update image
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.image)
# Repeat every 'interval' ms
self.window.after(self.interval, self.update_image)
if __name__ == "__main__":
root = tk.Tk()
root.geometry('600x800')
# root.resizable(width=0, height=0)
MainWindow(root, cv2.VideoCapture(0))
root.mainloop()
Thanks.
All you have to do is resize the image to fit the canvas size. You can either use cv2.resize() or Image.resize() to resize the image.
To get the current canvas size use canvas.winfo_height() and canvas.wifo_width(). Besides that, you should also consider updating the existing image using canvas.itemconfig(tag_id), instead of creating a new one each time.
sample code (I'll be using a label instead of canvas to display the image):
import tkinter as tk
from PIL import Image, ImageTk
import cv2
class MainWindow():
def __init__(self, window, cap):
self.window = window
self.cap = cap
self.interval = 20 # Interval in ms to get the latest frame
# Create canvas for image
self.vid_lbl = tk.Label(self.window)
self.vid_lbl.pack(fill="both", expand=True)
# Update image on canvas
self.update_image()
def update_image(self):
# Get the latest frame and convert image format
width, height = self.vid_lbl.winfo_width(), self.vid_lbl.winfo_height()
self.image = cv2.cvtColor(self.cap.read()[1], cv2.COLOR_BGR2RGB) # to RGB
self.image = cv2.resize(self.image, (width, height), cv2.INTER_AREA)
self.image = Image.fromarray(self.image) # to PIL format
#self.image = self.image.resize((width, height))
self.image = ImageTk.PhotoImage(self.image) # to ImageTk format
self.vid_lbl.config(image=self.image)
# Repeat every 'interval' ms
self.window.after(self.interval, self.update_image)
if __name__ == "__main__":
root = tk.Tk()
root.geometry('600x800')
# root.resizable(width=0, height=0)
MainWindow(root, cv2.VideoCapture(0))
root.mainloop()
Related
I tried to build off of the solution here. My code is:
from tkinter import mainloop, Tk, Frame, Button, Label, Canvas, PhotoImage, NW
from tkinter import ttk
from tkinter import filedialog
import tkinter as tk
from PIL import Image, ImageTk
class my_class(tk.Tk):
def __init__(self):
super().__init__()
self.geometry=('1400x1400')
self.filename = ''
my_notebook = ttk.Notebook(self)
my_notebook.pack(pady=5)
self.selections = Frame(my_notebook, width = 1100, height = 700)
self.selections.pack(fill = "both", expand=1)
my_notebook.add(self.selections, text = "Selections")
Button(self.selections, text = "Select an Image", command = self.get_image).place(x=10,y=40)
self.image_frame = Frame(my_notebook, width = 1100, height = 700)
self.image_frame.pack(fill = "both", expand=1)
my_notebook.add(self.image_frame, text = "Image")
self.my_canvas = Canvas(self.image_frame, width=800, height=600, bg="white")
self.my_canvas.pack()
self.rgb_var = tk.StringVar(self.image_frame, '0 0 0')
self.rgb_label = tk.Label(self.image_frame, textvariable = self.rgb_var)
self.rgb_label.pack()
self.image_frame.bind('<Motion>', lambda e: self.get_rgb(e))
def get_image(self):
self.filename = filedialog.askopenfilename(initialdir="D:/Python", title="select a file", filetypes = (("png files","*.png"),("jpg files","*.jpg")))
self.img = Image.open(self.filename)
self.img_rgb = self.img.convert('RGB')
dim_x, dim_y = self.img_rgb.size
self.img_tk = ImageTk.PhotoImage(self.img_rgb.resize((dim_x, dim_y)))
self.my_canvas.create_image(dim_x // 2, dim_y // 2, image = self.img_tk)
def get_rgb(self, event):
x, y = event.x, event.y
try:
rgb = self.img_rgb.getpixel((x, y))
self.rgb_var.set(rgb)
except IndexError:
pass # ignore errors if cursor is outside the image
if __name__ == '__main__':
app = my_class()
app.geometry=('1200x900')
app.mainloop()
I can use the button to select an image. Then I click the (Image) tab and see the selected image on the canvas.
I expected the (rgb_var) displayed under the image to update as I move the mouse pointer across the image. Instead the numbers under the image only update when the mouse pointer is in the frame, but outside the canvas. Also the numbers displayed seem to be unrelated to pixels in the image. How can I display the RGB values of a pixel that is (under the mouse pointer) when the mouse pointer is over the image?
I can't display images longer (height) than about 30612 pixels high. I've read that there is a maximum height to canvas. I'd like to get the source file and extend that to 90 or 100k pixels in height. Conversely, I've seen suggested that a canvas may be buffered, if this is true, I have no clue how to implement it.. Any help is appreciated!
I am using code I found off Stack that is supposed to deal with large images, it does alright, but ultimately hit's the cavas height limit.
Canvas Limit
from tkinter import *
from PIL import ImageTk
from PIL import *
Image.MAX_IMAGE_PIXELS = None
class ScrolledCanvas(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.master.title("Spectrogram Viewer")
self.pack(expand=YES, fill=BOTH)
canv = Canvas(self, relief=SUNKEN)
canv.config(width=400, height=500)
# canv.config(scrollregion=(0,0,1000, 1000))
# canv.configure(scrollregion=canv.bbox('all'))
canv.config(highlightthickness=0)
sbarV = Scrollbar(self, orient=VERTICAL)
sbarH = Scrollbar(self, orient=HORIZONTAL)
sbarV.config(command=canv.yview)
sbarH.config(command=canv.xview)
canv.config(yscrollcommand=sbarV.set)
canv.config(xscrollcommand=sbarH.set)
sbarV.pack(side=RIGHT, fill=Y)
sbarH.pack(side=BOTTOM, fill=X)
canv.pack(side=LEFT, expand=YES, fill=BOTH)
self.im = Image.open("Test_3.tif")
width, height = self.im.size
canv.config(scrollregion=(0, 0, width, height))
self.im2 = ImageTk.PhotoImage(self.im)
self.imgtag = canv.create_image(0, 0, anchor="nw", image=self.im2)
ScrolledCanvas().mainloop()
I tried to put together a bigger image from displays of a grid of canvases. This looks like it might work, at least if you just want to display a big image. I have just tested with a small image and not paid any attention to memory or speed or anything...
from tkinter import *
from scrframe import VerticalScrolledFrame
root = Tk()
tiles = VerticalScrolledFrame(root) # Scrolled frame
tiles.grid()
tw = 90 # Tile width
th = 110 # Tile height
rows = 4 # Number of tiles/row
cols = 4 # Number of tiles/column
tile_list = [] # List of image tiles
img = PhotoImage(file='pilner.png')
for r in range(rows):
col_list = []
for c in range(cols):
tile = Canvas(tiles.interior, highlightthickness=0, bg='tan1',
width=tw, height=th)
tile.create_image(-c*tw, -r*th, image=img, anchor ='nw')
tile.grid(row=r, column=c)
col_list.append(tile)
tile_list.append(col_list)
root.mainloop()
Now, scrolling a frame seems to raise some problems, but there also seems to be solutions. I tried to use VerticalScrolledFrame as described in Python Tkinter scrollbar for frame and it works fine. As it only provides for a vertical scrollbar you'd have to implement horizontal scrollbar yourself. Maybe a few additional functions as scrolling with the mouse wheel, keyboard shortcuts or other would be useful.
I got the VerticalScrolledFrame from TKinter scrollable frame and modified it for Python 3.
This is the code I've come up with from several sources - Thanks to figbeam for all the help. Also, this is not pretty!!!! The button shows up in the center of the Tkinter window. If you'd like to modify this, please do.
from tkinter import *
from PIL import ImageTk as itk
from PIL import Image
import math
import numpy as np
Image.MAX_IMAGE_PIXELS = None #prevents the "photo bomb" warning from popping up. Have to have this for really large images.
#----------------------------------------------------------------------
# makes a simple window with a button right in the middle that let's you go "down" an image.
class MainWindow():
#----------------
def __init__(self, main):
# canvas for image
_, th, tw, rows, cols = self.getrowsandcols()
self.canvas = Canvas(main, width=tw, height=th)
self.canvas.grid(row=0, column=0)
# images
self.my_images = self.cropimages() # crop the really large image down into several smaller images and append to this list
self.my_image_number = 0 #
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(0, 0, anchor = NW, image = self.my_images[self.my_image_number])
# button to change image
self.button = Button(main, text="DOWN", command=self.onDownButton)
self.button.grid(row=0, column=0)
#----------------
def getimage(self):
im = Image.open("Test_3.png") # import the image
im = im.convert("RGBA") # convert the image to color including the alpha channel (which is the transparency best I understand)
width, height = im.size # get the width and height
return width, height, im # return relevent variables/objects
def getrowsandcols(self):
width, height, im = self.getimage()
im = np.asarray(im) # Convert image to Numpy Array
tw = width # Tile width will equal the width of the image
th = int(math.ceil(height / 100)) # Tile height
rows = int(math.ceil(height / th)) # Number of tiles/row
cols = int(math.ceil(width / tw)) # Number of tiles/column
return im, th, tw, rows, cols #return selected variables
def cropimages(self):
self.my_images = [] # initialize list to hold Tkinter "PhotoImage objects"
im, th, tw, rows, cols = self.getrowsandcols() # pull in needed variables to crop the really long image
for r in range(rows): # loop row by row to crop all of the image
crop_im =im[r * th:((r * th) + th), 0:tw] # crop the image for the current row (r). (th) stands for tile height.
crop_im = Image.fromarray(crop_im) # convert the image from an Numpy Array to a PIL image.
crop_im = itk.PhotoImage(crop_im) # convert the PIL image to a Tkinter Photo Object (whatever that is)
self.my_images.append(crop_im) # Append the photo object to the list
crop_im = None
return self.my_images
def onDownButton(self):
# next image
self.my_image_number += 1 #every button pressed will
# return to first image
if self.my_image_number == len(self.my_images):
self.my_image_number = 0
# change image
self.canvas.itemconfig(self.image_on_canvas, image = self.my_images[self.my_image_number]) #attaches the image from the image list to the canvas
#----------------------------------------------------------------------
root = Tk()
MainWindow(root)
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.
Trying to set up a background for my tkinter window. I have a square background image, which fades to black around the edges, and then the main window has a black background. The image is placed over the background, and if the window is wider than it is tall, the image centers itself in the middle over the black background, and it all looks very nice.
However when the window is smaller than the image in width and height, it puts the center of the image in the center of the window, so you don't see the whole image, and it looks a little odd. Is there a way of resizing the image so that if the largest of the width and height of the window is smaller than the image, the image is adjusted to that size, keeping aspect ratio.
So say the background image is 600x600:
In a 800x400 window, the image does not resize, and centers itself vertically.
In a 500x400 window, the image resizes to 500x500, and still centers itself vertically.
In a 400x900 window, the image does not resize, and centers itself horizontally.
The centering functionality is already there, I just need the resize functionality.
Currently what I have is:
from tkinter import *
root = Tk()
root.title("Title")
root.geometry("600x600")
root.configure(background="black")
background_image = PhotoImage(file="Background.gif")
background = Label(root, image=background_image, bd=0)
background.pack()
root.mainloop()
Not sure if there is a way of doing this in tkinter? Or if perhaps I would write my own function that resizes the image according to the window size, however the image needs to resize relatively smoothly and quickly if the user resizes the window at any point.
This is example application that uses Pillow to resize image on the Label as the label changes size:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
root.title("Title")
root.geometry("600x600")
root.configure(background="black")
class Example(Frame):
def __init__(self, master, *pargs):
Frame.__init__(self, master, *pargs)
self.image = Image.open("./resource/Background.gif")
self.img_copy= self.image.copy()
self.background_image = ImageTk.PhotoImage(self.image)
self.background = Label(self, image=self.background_image)
self.background.pack(fill=BOTH, expand=YES)
self.background.bind('<Configure>', self._resize_image)
def _resize_image(self,event):
new_width = event.width
new_height = event.height
self.image = self.img_copy.resize((new_width, new_height))
self.background_image = ImageTk.PhotoImage(self.image)
self.background.configure(image = self.background_image)
e = Example(root)
e.pack(fill=BOTH, expand=YES)
root.mainloop()
This is how it works using Lenna image as example:
I have modified the above code so it is not in a class
#!/usr/bin/python3.5
from tkinter import *
from tkinter import ttk
from PIL import Image, ImageTk
root = Tk()
root.title("Title")
root.geometry('600x600')
def resize_image(event):
new_width = event.width
new_height = event.height
image = copy_of_image.resize((new_width, new_height))
photo = ImageTk.PhotoImage(image)
label.config(image = photo)
label.image = photo #avoid garbage collection
image = Image.open('image.gif')
copy_of_image = image.copy()
photo = ImageTk.PhotoImage(image)
label = ttk.Label(root, image = photo)
label.bind('<Configure>', resize_image)
label.pack(fill=BOTH, expand = YES)
root.mainloop()
Just sugesting a slight change in the answer. Using self.master.winfo_width(),self.master.winfo_height() instead of 'event' makes he adjustment to size much quicker.
import tkinter as tk
from PIL import Image, ImageTk
class Layout:
def __init__(self,master):
self.master = master
self.rootgeometry()
self.canvas = tk.Canvas(self.master)
self.canvas.pack()
self.background_image = Image.open('image_file.PNG')
self.image_copy = self.background_image.copy()
self.background = ImageTk.PhotoImage(self.background_image)
self.loadbackground()
def loadbackground(self):
self.label = tk.Label(self.canvas, image = self.background)
self.label.bind('<Configure>',self.resizeimage)
self.label.pack(fill='both', expand='yes')
def rootgeometry(self):
x=int(self.master.winfo_screenwidth()*0.7)
y=int(self.master.winfo_screenheight()*0.7)
z = str(x) +'x'+str(y)
self.master.geometry(z)
def resizeimage(self,event):
image = self.image_copy.resize((self.master.winfo_width(),self.master.winfo_height()))
self.image1 = ImageTk.PhotoImage(image)
self.label.config(image = self.image1)
root = tk.Tk()
a = Styling.Layout(root)
root.mainloop()
i have created function for calling resize a single time with methods after et after cancel
def on_resize(self, evt):
if self.inter == 0:
self.inter = 1
self.minuteur = self.fenetrePrincipale.after(100, self.endResize)
else:
self.minuteur = self.fenetrePrincipale.after_cancel(self.minuteur)
self.minuteur = self.fenetrePrincipale.after(100, self.endResize)
def endResize(self):
self.inter = 0
self.fenetrePrincipale.background = self.fenetrePrincipale.background.resize((self.fenetrePrincipale.winfo_width(), self.fenetrePrincipale.winfo_height()))
self.pixi = ImageTk.PhotoImage(self.fenetrePrincipale.background)
self.canvas.configure(width=self.fenetrePrincipale.winfo_width(), height=self.fenetrePrincipale.winfo_height())
self.canvas.create_image(0, 0, anchor=NW, image=self.pixi)
Here is the principle, after defines a timer and a function to be recalled at the end, after_cancel cleans the timer so each iteration of the function cleans the timer and starts it, at the last iteration of resize the timer remains triggered.
for more information on cancel and timer with after:
after detailled
Can anyone help me to resize the image using ImageTk?
I have a canvas and I will put pictures there.
I have different kinds of pictures = different sizes for all pictures
And when I attach the picture (just one) in the canvas, I want the picture's size to resize so that it will fit in the canvas and it will still maintain its proportion.
Please help me! I am new in PIL, Tkinter,and Python.
Update:
I tried using thumbnail under Image but in resizing:
self.image.thumbnail(self.picsize,Image.ANTIALIAS)
the image is not fitting the canvas size and if the image is longer/wider than the canvas, it is just cut. (Not resizing to fit into the canvas)
Code:
from PIL import ImageTk
from Tkinter import *
import os,tkFileDialog,Image
picsize = 250,250 # I want to set this so that it will fit in the self.imagecanvas | Every images attached will share same Size
imagepath = "Default_ProPic.jpg"
class GUI():
global picsize
def display(self):
self.canvas = Canvas(width=1200,height=700)
self.canvas.pack()
self.imagecanvas = Canvas(self.canvas,width=400,height=400)
self.imagecanvas.place(x=980,y=180)
self.image = Image.open(imagepath)
self.image.thumbnail(picsize,Image.ANTIALIAS)
self.newimage = ImageTk.PhotoImage(self.image)
self.profile_picture=self.imagecanvas.create_image(0,0,anchor = NW,image=self.newimage)
attachbutton = Button(self.canvas,text=" Profile Pic ",command=lambda:self.attachpic())
attachbutton.place(x=1030,y=320)
mainloop()
def attachpic(self):
global picsize
attachphoto = tkFileDialog.askopenfilename(title="Attach photo")
self.image = Image.open(attachphoto)
self.image.thumbnail(picsize,Image.ANTIALIAS)
self.newimage = ImageTk.PhotoImage(self.image)
self.imagecanvas.itemconfigure(self.profile_picture, image=self.newimage)
GUI = GUI()
GUI.display()
Picture used above:
Try saving the thumbnail as a separate variable:
self.thmb_img = self.image.thumbnail(picsize, Image.ANTIALIAS)
I suspect it may be taking the original self.image = Image.open(attachphoto)
I would suggest watching what sizing is going on with:
def attachpic(self):
picsize = 250, 250
attachphoto = tkFileDialog.askopenfilename(title="Attach photo")
self.image = Image.open(attachphoto)
print self.image.size()
self.thmb_img = self.image.thumbnail(picsize,Image.ANTIALIAS)
print self.thmb_img.size()
Check the output size and verify it is the same as the original and the desired (250, 250) thumbnail.