I am currently trying to display images on a canvas. More specifically, I would like to have the images that are drawn on the canvas to be resized based on the size of the window (this way the images are always going to fit on the canvas).
I start off with a simple canvas that fills the entire screen.
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
WIDTH, HEIGHT = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry('%dx%d+0+0' % (WIDTH, HEIGHT))
canvas = tk.Canvas(root)
canvas.pack(fill="both", expand=True)
I then get set up with my background images and images that will be able to get resized on the background.
backgroundImage = ImageTk.PhotoImage(Image.open("filepath"))
image1 = Image.open("filepath")
image2 = ...
....
....
I then created a method for resizing the images.
"""
This method resizes a image so that all the images fits on the GUI. This first creates an Image object,
but since the Image object does not allow access to the width and height of the Image object, a ImageTk
object needs to be created from the Image object. The ImageTk object cannot resize, but the Image object
can. So using ImageTk object to get the height and width and the Image object to resize, a new Image object
that is resized to fit the GUI is created.
#imageFile- the image file that is being resized
#windowMeasure- the measurement of the window to proportionally resize the image
#useHeight- determine the measurement being proportioned
"""
def resizedImageTk(image, windowMeasure, useHeight):
imageTk = ImageTk.PhotoImage(image)
area = windowMeasure * 4/5
tileSize = area / 4
if useHeight:
proportion = tileSize / imageTk.height()
else:
proportion = tileSize / imageTk.width()
resizedImage = image.resize((int(imageTk.width()*proportion), int(imageTk.height()*proportion)))
resizedImageTk = ImageTk.PhotoImage(resizedImage)
return resizedImageTk
I then use a method for redrawing the resized images when there is a change to the size of the window and bind it to the root. Note: I know that this can be extremely computational and so I have reduced the number of times this occurs
numResizes = 0
def handle_configure(event):
if numResizes % 5 == 0:
geometry = root.geometry()
width = int(geometry[0:geometry.index("x")])
height = int(geometry[geometry.index("x")+1:geometry.index("+")])
canvas.create_image((0,0), image=backgroundImageTk, anchor="nw")
if height < width:
measurement = height
else:
measurement = width
resizedImage1 = resizedImageTk(image1, measurement, height < width)
resizedImage2 = ....
....
....
images = [resizedImage1, resizedImage2, ...]
imageWidth = resizedImage1.width()
imageHeight = resizedImage1.height()
i = 0
for row in range(0, int(len(images) / 4)):
for column in range(0, int(len(images) / 5):
x = imageWidth*column + int(width/2) - imageWidth * 2
y = imageHeight*row + int(height/2) - int(imageHeight*2.5)
canvas.create_image((x, y), image=images[i])
i=i+1
numResizes = numResizes + 1
root.bind("<Configure>", handle_configure)
root.mainloop()
I have run this with my images and have had some success, however, it does not work completely. I have my background image get displayed but my other images do not. I do not know why since when I use the create_line function for the canvas in the nested for loop (where the images are not being shown), I do get the line showing.
If someone could give some advice on this, I would appreciate it!
Thanks
Update:
Here is a simple sample of what I am trying to do. You can use any sample image to test this.
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
WIDTH, HEIGHT = int(root.winfo_screenwidth() * 103/104), int(root.winfo_screenheight() * 11/12)
root.geometry('%dx%d+0+0' % (WIDTH, HEIGHT))
canvas = tk.Canvas(root)
canvas.pack(fill="both", expand=True)
testImage = Image.open("enter file path here!")
testImageTk = ImageTk.PhotoImage(testImage)
resizedTestImage = None
resizedTestImageTk = None
def handle_configure(event):
geometry = root.geometry()
width = int(geometry[0:geometry.index("x")])
height = int(geometry[geometry.index("x")+1:geometry.index("+")])
useHeight = height < width
if useHeight:
measurement = height
else:
measurement = width
if useHeight:
proportion = measurement / testImageTk.height()
else:
proportion = measurement / testImageTk.width()
resizedTestImage = testImage.resize((int(testImageTk.width()*proportion), int(testImageTk.height()*proportion)))
resizedTestImageTk = ImageTk.PhotoImage(resizedTestImage)
canvas.create_image((0,0), image=resizedTestImageTk, anchor="nw")
print("(image width, image height): (" + str(resizedTestImageTk.width()) + " " + str(resizedTestImageTk.height()) + ")")
root.bind("<Configure>", handle_configure)
root.mainloop()
Your new code has problem with bug in PhotoImage.
Inside handle_configure you assign to local variable resizedTestImageTk even if you already created external/global variable resizedTestImageTk
It works for me if I inform function that it has to use global to inform function that it has to assign it to external variable instead of local one
def handle_configure(event):
global resizedTestImageTk # <-- inform function to assign image to global variable instead of using local one
or I assign local resizedTestImageTk to global class.
canvas.resizedTestImageTk = resizedTestImageTk # <-- assign local `resizedTestImageTk` to global class.
Minimal working code.
I changed some calculations to make it more readable.
import tkinter as tk
from PIL import Image, ImageTk
# --- functions ---
def handle_configure(event):
global resizedTestImageTk # <-- inform function to assign image to global variable instead of using local one
geometry = root.geometry()
window_width = int(geometry[0:geometry.index("x")])
window_height = int(geometry[geometry.index("x")+1:geometry.index("+")])
image_width = testImageTk.height()
image_height = testImageTk.width()
if window_height < window_width:
proportion = window_height / image_height
else:
proportion = window_width / image_width
image_width = int(image_width * proportion)
image_height = int(image_height * proportion)
resizedTestImage = testImage.resize((image_width, image_height))
resizedTestImageTk = ImageTk.PhotoImage(resizedTestImage)
canvas.create_image((0,0), image=resizedTestImageTk, anchor="nw")
#canvas.resizedTestImageTk = resizedTestImageTk # <-- assign local `resizedTestImageTk` to global class.
print(f"(image width, image height): ({image_width} {image_height})")
# --- main ---
root = tk.Tk()
WIDTH = int(root.winfo_screenwidth() * 103/104)
HEIGHT = int(root.winfo_screenheight() * 11/12)
root.geometry('%dx%d+0+0' % (WIDTH, HEIGHT))
canvas = tk.Canvas(root)
canvas.pack(fill="both", expand=True)
testImage = Image.open("lenna.png")
testImageTk = ImageTk.PhotoImage(testImage)
resizedTestImage = None
resizedTestImageTk = None
root.bind("<Configure>", handle_configure)
root.mainloop()
And image for test
Wikipedia: Lenna
EDIT:
I see other problem - not so visible. You always put new image on but you don't remove old image - so finally it may use more memory.
You could put image on canvas at start - and get its ID
image_id = canvas.create_image((0,0), image=testImageTk, anchor="nw")
and later replace image using ID
canvas.itemconfig(image_id, image=resizedTestImageTk)
Minimale working code:
import tkinter as tk
from PIL import Image, ImageTk
# --- functions ---
def handle_configure(event):
global resizedTestImageTk # <-- inform function to assign image to global variable instead of using local one
geometry = root.geometry()
window_width = int(geometry[0:geometry.index("x")])
window_height = int(geometry[geometry.index("x")+1:geometry.index("+")])
image_width = testImageTk.width()
image_height = testImageTk.height()
if window_height < window_width:
proportion = window_height / image_width
else:
proportion = window_width / image_height
image_width = int(image_width * proportion)
image_height = int(image_height * proportion)
resizedTestImage = testImage.resize((image_width, image_height))
resizedTestImageTk = ImageTk.PhotoImage(resizedTestImage)
canvas.itemconfig(image_id, image=resizedTestImageTk)
#canvas.resizedTestImageTk = resizedTestImageTk # <-- assign local `resizedTestImageTk` to global class.
print(f"(image width, image height): ({image_width} {image_height})")
# --- main ---
image_id = None # default value at start
root = tk.Tk()
WIDTH = int(root.winfo_screenwidth() * 103/104)
HEIGHT = int(root.winfo_screenheight() * 11/12)
root.geometry('%dx%d+0+0' % (WIDTH, HEIGHT))
canvas = tk.Canvas(root)
canvas.pack(fill="both", expand=True)
testImage = Image.open("lenna.png")
testImageTk = ImageTk.PhotoImage(testImage)
resizedTestImage = None
resizedTestImageTk = None
image_id = canvas.create_image((0,0), image=testImageTk, anchor="nw")
root.bind("<Configure>", handle_configure)
root.mainloop()
Related
im 100% new to coding and just started to follow some guides a few days ago. I am now making a digital clock for my desktop and i want to change my background to a custom image that i have.
Can someone explain to me where i can change it so that the background becomes my image that i have? BR
from tkinter import Tk
from tkinter import Label
import time
from PIL import Image, ImageTk
root = Tk()
root.title("Klocka")
root.attributes("-topmost", 1)
root.attributes('-alpha', 1)
root.iconbitmap('klocka.ico (1).ico')
root.geometry('600x400+50+50')
window_width = 255
window_height = 50
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int(screen_width/3500 - window_width / 0.97)
center_y = int(screen_height/1 - window_height / 0.62)
root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
def present_time():
display_time = time.strftime("%I:%M:%S")
digi_clock.config(text=display_time)
digi_clock.after(200,present_time)
digi_clock = Label(root, font=("Arial",50),bg="black",fg="red")
digi_clock.pack()
present_time()
root.mainloop()
you can try image path and background image concept. First you have to choose image, then it's size and it's position.
IMAGE_PATH = 'PicNameHere.png'
WIDTH, HEIGTH = 200, 50
bkrgframe = BkgrFrame(root, IMAGE_PATH, WIDTH, HEIGTH)
bkrgframe.pack()
Code sample is below.
from tkinter import Tk
from tkinter import Label
import time
from PIL import Image, ImageTk
IMAGE_PATH = 'PicNameHere.png'
WIDTH, HEIGTH = 200, 50
root = Tk()
root.title("Klocka")
root.attributes("-topmost", 1)
root.attributes('-alpha', 1)
root.iconbitmap('klocka.ico (1).ico')
root.geometry('600x400+50+50')
window_width = 255
window_height = 50
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int(screen_width/3500 - window_width / 0.97)
center_y = int(screen_height/1 - window_height / 0.62)
root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
bkrgframe = BkgrFrame(root, IMAGE_PATH, WIDTH, HEIGTH)
bkrgframe.pack()
def present_time():
display_time = time.strftime("%I:%M:%S")
digi_clock.config(text=display_time)
digi_clock.after(200,present_time)
digi_clock = Label(root, font=("Arial",50),bg="black",fg="red")
digi_clock.pack()
present_time()
root.mainloop()
You can show both an image and text in a Label widget using text, image and compound options:
img_tk = ImageTk.PhotoImage(file="path/to/image.png")
digi_clock = Label(root, font=("Arial",50), fg="red", image=img_tk, compound="center")
digi_clock.pack()
from tkinter import Tk
from tkinter import Label
import time
from PIL import Image, ImageTk
root = Tk()
root.title("Klocka")
root.attributes("-topmost", 1)
root.attributes('-alpha', 1)
root.iconbitmap('klocka.ico (1).ico')
root.geometry('600x400+50+50')
bg = PhotoImage(file = "Your_img.png")
canvas1 = Canvas( root, width = 400,
height = 400)
canvas1.pack(fill = "both", expand = True)
canvas1.create_image( 0, 0, image = bg,
anchor = "nw")
window_width = 255
window_height = 50
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int(screen_width/3500 - window_width / 0.97)
center_y = int(screen_height/1 - window_height / 0.62)
root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
def present_time():
display_time = time.strftime("%I:%M:%S")
digi_clock.config(text=display_time)
digi_clock.after(200,present_time)
digi_clock = Label(root, font=("Arial",50),bg="black",fg="red")
digi_clock.pack()
present_time()
root.mainloop()
I created this little algorithm that is supposed to draw an image (imagine that my brush is an image) so that when I keep clicking I will draw the image, but as you can see if you test the code, it does not paint.
What it does is just moves the image across the Canvas.
Is there a way for the image to remain on the Canvas?
Here is my code:
from tkinter import *
from PIL import Image, ImageTk
master = Tk()
w = Canvas(master, width=800, height=400)
w.pack(expand = YES, fill = BOTH)
imagen = Image.open('C:/Users/Andres/Desktop/hola.png')
P_img = ImageTk.PhotoImage(imagen)
def paint( event ):
global w, P_img_crop
#I get the mouse coordinates
x, y = ( event.x - 1 ), ( event.y - 1 )
#I open and draw the image
img_crop = Image.open('C:/Users/Andres/Desktop/papa.png')
P_img_crop = ImageTk.PhotoImage(img_crop)
w.create_image((x,y), anchor=NW, image=P_img_crop)
w.bind( "<B1-Motion>", paint )
mainloop()
I GOT IT
I did not know that the image that was drawn on the canvas, should be saved, so what I did is store the images in a matrix, which belongs to the canvas.
Here is the code, just in case ...
from tkinter import *
from PIL import Image, ImageTk
master = Tk()
w = Canvas(master, width=800, height=400)
w.dib = [{} for k in range(10000)]
w.pack(expand = YES, fill = BOTH)
puntero = 0
def paint( event ):
global w, P_img_crop, puntero
#I get the mouse coordinates
x, y = ( event.x - 1 ), ( event.y - 1 )
#I open and draw the image
img_crop = Image.open('C:/Users/Andres/Documents/PROYECTOS INCONCLUSOS/PAINT MATEW PYTHON/hola.png')
w.dib[puntero]['image'] = ImageTk.PhotoImage(img_crop)
w.create_image((x,y), anchor=NW, image=w.dib[puntero]['image'])
puntero += 1
if(puntero >=10000):
puntero = 0
w.bind( "<B1-Motion>", paint )
mainloop()
All you need to do remove the image creation inside the paint() function. Then you will achieve what you want because otherwise it creates the image again and doesn't save a copy behind. In other words, when you move the brush, the previous image is garbage collected.
Code:
from tkinter import *
from PIL import Image, ImageTk
master = Tk()
w = Canvas(master, width=800, height=400)
w.pack(expand = YES, fill = BOTH)
img_crop = Image.open('yes.png')
P_img_crop = ImageTk.PhotoImage(img_crop)
def paint(event):
global P_img_crop
#I get the mouse coordinates
x, y = event.x - 1, event.y - 1
#I open and draw the image
w.create_image(x, y, image = P_img_crop)
w.bind("<B1-Motion>", paint)
master.mainloop()
Hope this helps!
I'm writing a simple bouncing icon program (Python 3.7, Windows 10 x64) to get the feel for Tkinter and canvases. I've posted my code below. My problem with the program is that it clips the edges of the icon (in the direction of motion). If I slow the motion down a bit (by increasing the value in the after method) it no longer clips, but the motion is choppy. Maybe I'm overthinking this, it basically does what I've aimed for. But if this were a game or other project that mattered, how would this be prevented?
from tkinter import *
import os
from PIL import Image, ImageTk
xinc, yinc = 5, 5
def load_image(width, height, imgpath):
loadimg = Image.open(imgpath)
pwid, phi = loadimg.size
pf1, pf2 = 1.0*width/pwid, 1.0*height/phi
pfactor = min([pf1, pf2])
pwidth, pheight = int(pwid*pfactor), int(phi*pfactor)
loaded = loadimg.resize((pwidth, pheight), Image.ANTIALIAS)
loaded = ImageTk.PhotoImage(loaded)
return loaded
def bounce():
global xinc
global yinc
cwid = int(dash.cget('width'))
chi = int(dash.cget('height'))
x = dash.coords(dashposition)[0]
y = dash.coords(dashposition)[1]
if x > cwid-10 or x < 10:
xinc = -xinc
if y > chi-10 or y < 10:
yinc = -yinc
dash.move(dashposition, xinc, yinc)
dash.after(15, bounce)
root = Tk()
root.configure(bg='black')
dash = Canvas(root, bg='black', highlightthickness=0, width=400, height=300)
dash.grid(row=0, column=0, padx=2, pady=2)
imagepath = os.getcwd() + '/img/cloudy.png'
image = load_image(20, 20, imagepath)
x, y = 10, 10
dashposition = dash.create_image(x, y, anchor=CENTER, image=image, tags=('current'))
bounce()
root.mainloop()
cloudy.png
There are two contributing factors to the clipping. The main problem is that load_image(20, 20, imagepath) will only result in a 20x20 object if the original image is square. But your cloud object isn't square. And your border collision calculations will only work if the rescaled cloud object is 20x20. So we need to modify that. The other issue is that you aren't compensating for the Canvas's border. The easy way to do that is to set it to zero with bd=0.
Its usually a good idea during GUI development to use various colors so you know exactly where your widgets are. So that we can more easily see when the cloud hits the Canvas border I've set the root window color to red. I also increased the .after delay, because I just couldn't see what was happening at the speed you set it at. ;) And I made the cloud a bit bigger.
I've made a couple of other minor changes to your code, the main one being that I got rid of that "star" import, which dumps over 100 Tkinter names into your namespace.
Update
I've reduced xinc & yinc to 1, and improved the bounce bounds calculation. (And incorporated jasonharper's suggestion re cwid & chi). I'm no longer seeing any clipping on my machine, and the motion is smoother. I also reduced the Canvas padding to 1 pixel, but that should have no effect on clipping. I just tried it with padx=10, pady=10 and it works as expected.
import tkinter as tk
import os
from PIL import Image, ImageTk
def load_image(width, height, imgpath):
loadimg = Image.open(imgpath)
pwid, phi = loadimg.size
pf1, pf2 = width / pwid, height / phi
pfactor = min(pf1, pf2)
pwidth, pheight = int(pwid * pfactor), int(phi * pfactor)
loaded = loadimg.resize((pwidth, pheight), Image.ANTIALIAS)
loaded = ImageTk.PhotoImage(loaded)
return loaded, pwidth // 2, pheight // 2
xinc = yinc = 1
def bounce():
global xinc, yinc
x, y = dash.coords(dashposition)
if not bx <= x < cwid-bx:
xinc = -xinc
if not by <= y < chi-by:
yinc = -yinc
dash.move(dashposition, xinc, yinc)
dash.after(5, bounce)
root = tk.Tk()
root.configure(bg='red')
dash = tk.Canvas(root, bg='black',
highlightthickness=0, width=800, height=600, bd=0)
dash.grid(row=0, column=0, padx=1, pady=1)
cwid = int(dash.cget('width'))
chi = int(dash.cget('height'))
imagepath = 'cloudy.png'
size = 50
image, bx, by = load_image(size, size, imagepath)
# print(bx, by)
dashposition = dash.create_image(bx * 2, by * 2,
anchor=tk.CENTER, image=image, tags=('current'))
bounce()
root.mainloop()
Here is my code, you can ignore most of them but only see the last part which have #
import tkinter as tk
from PIL import ImageTk, Image
def bresize_and_load(path):
global bwidth, bheight
im = Image.open(path)
bwidth,bheight = im.size
resized = bresizemode(im, bwidth, bheight)
width,height = resized.size
return ImageTk.PhotoImage(resized)
def bresizemode(im, width, height):
if height / width >= ratio:
return im.resize((int(round((width / height) * usable_height)), usable_height),
Image.ANTIALIAS)
if height / width < ratio:
return im.resize((usable_width, (int(round((height / width) * usable_width)))),
Image.ANTIALIAS)
root = tk.Tk()
root.state("zoomed")
root.resizable(width=False, height=False)
frame = tk.Frame(root)
frame.grid(row = 0, column = 0, sticky = 'nsew')
tk.Grid.rowconfigure(root, 0, weight=1)
tk.Grid.columnconfigure(root, 0, weight=1)
row = 4
column = 5
for ro in range(row):
tk.Grid.rowconfigure(frame, ro, weight=1)
for co in range(column):
tk.Grid.columnconfigure(frame, co, weight=1)
root.update()
f_width = frame.winfo_width()
f_height = frame.winfo_height()
booklistbutton = []
for i in range(row):
for e in range(column):
bbutton = tk.Button(frame, height = int(f_height / row),
width = int(f_width / column))
bbutton.grid(row = i, column = e)
booklistbutton.append(bbutton)
root.update()
usable_width = booklistbutton[0].winfo_width()
usable_height = booklistbutton[0].winfo_height()
ratio = usable_height / usable_width
#here is image path
path = 'sample.jpg'
imm = []
#if it is range(20)(just = row * column) or less than column(here is 5), it work fine
for i in range(20):
imm.append(bresize_and_load(path))
booklistbutton[i].config(image = imm[i])
root.mainloop()
My question is, if you load image in button, but the number of imaged buttons is not less than column or equal row * column, the imaged buttons will disappear.
When range equal row * column(20):
When range is 6:
This is weird for me, does anyone have any idea?
Also, if you do not set button's width and height, they won't disappear. But buttons will little bigger than images.
(Posted solution on behalf of the OP).
I find the problem by myself, the problem is when I set the Buttons' size, it is chr size, but when I load a image, it change to pixel size, and at the same size number, chr size is bigger and bigger than pixel size, so the imaged button become too small to show.
I am a python newbie and have been making a somewhat odd slideshow script that cycles through images and also sources a variable from another file to 'settle' on an image.
I'm sure my code is tragic. But it does work (see below)!
My question is - how would I make it fade between images instead of the jerky go to white momentarily then to next image which it does currently? Is there a transitions module I should look at?
from Tkinter import *
import Image, ImageTk, random, string
class MyApp(Tk):
def __init__(self):
Tk.__init__(self)
fr = Frame(self)
fr.pack()
self.canvas = Canvas(fr, height = 400, width = 600)
self.canvas.pack()
self.old_label_image = None
self.position = 0
self.command = 0
self.oldcommand = 0
self.slideshow()
self.debug()
def debug(self):
self.QUIT = Button(self)
self.QUIT["text"] = "QUIT!" + str(self.command)
self.QUIT["fg"] = "red"
self.QUIT["command"] = self.quit
self.QUIT.pack({"side": "right"})
def slideshow (self):
if self.command != self.oldcommand:
self.after_cancel(self.huh)
# run through random between 2-5 changes
# then settle on command for 30 seconds
self.title("Title: PAUSE")
self.oldcommand = self.command
self.slideshow()
else:
file = str(self.position) + '.jpg'
image1 = Image.open(file)
self.tkpi = ImageTk.PhotoImage(image1)
label_image = Label(self, image=self.tkpi)
label_image.place(x=0,y=0,width=image1.size[0],height=image1.size[1])
self.title("Title: " + file)
if self.old_label_image is not None:
self.old_label_image.destroy()
self.old_label_image = label_image
# make this random instead of pregressional
if self.position is not 1:
self.position = self.position + 1
else:
self.position = 0
commandfile = open('command.txt', 'r')
self.command = string.atoi(commandfile.readline())
commandfile.close()
int = random.randint(2000, 5000)
self.huh = self.after(int, self.slideshow)
#self.after_cancel(huh) - works ! so maybe can do from below Fn?
if __name__ == "__main__":
root = MyApp()
root.mainloop()
This can be achieved using the blend function.
Image.blend(image1, image2, alpha) ⇒ image
Creates a new image by interpolating between the given images, using a constant alpha. Both images must have the same size and mode.
out = image1 * (1.0 - alpha) + image2 * alpha
If the alpha is 0.0, a copy of the first image is returned. If the alpha is 1.0, a copy of the second image is returned. There are no restrictions on the alpha value. If necessary, the result is clipped to fit into the allowed output range.
So you could have something like this:
alpha = 0
while 1.0 > alpha:
image.blend(img1,img2,alpha)
alpha = alpha + 0.01
label_image.update()
An example is here, havn't had time to test this but you get the idea-
from PIL import image
import time
white = image.open("white_248x.jpg")
black = image.open("black_248x.jpg")
new_img = image.open("white_248x.jpg")
root = Tk()
image_label = label(root, image=new_img)
image_label.pack()
alpha = 0
while 1.0 > alpha:
new_img = image.blend(white,black,alpha)
alpha = alpha + 0.01
time.sleep(0.1)
image_label.update()
root.mainloop()