Display images in tkinter canvas - python

I'm getting some issues trying to creating canvas images, a class handles the creation of the images, and i want that the class creates as many images as the times that i call it.
My code is this
from tkinter import *
from random import *
canvas_width = 800
canvas_height = 800
master = Tk()
canvas = Canvas(master, width=canvas_width, height=canvas_height, bg="black")
canvas.pack()
def images():
for _ in range(3):
Image_creator().create_image()
class Image_creator:
def create_image(self):
start_x = randint(1, canvas_width//2)
start_y = randint(1, canvas_height//2)
img = PhotoImage(file="pac_inizio.png")
master.img = img
self.image = canvas.create_image(start_x, start_y, anchor=NW, image=img)
images()
mainloop()
Actually with this code is displayed only 1 of the 3 images, i think that the other 2 canvas images are created but without the image inside.
I tried to change the create_image function for create buttons instead of canvas images and understand if it was actually as i thought.
If u run the code with the modified function it diplays 3 buttons but only one with the image inside.
def create_image(self):
start_x = randint(1, canvas_width//2)
start_y = randint(1, canvas_height//2)
img = PhotoImage(file="pac_inizio.png")
master.img = img
self.image = Button( anchor=NW, image=img)
self.image.place(x=start_x, y=start_y)
I think that the issue is in the image reference but don't know how to solve it

You seem to be aware of the common problem of images in tkinter being garbage collected although they are still being used somewhere, unless there's an explicit reference to that images. Note, however, that with master.img = img you will always only have a reference to one of the three (identical) images. If those images are meant to be different in your actual code, make master.img a list (or use any other list in the global score to store the images).
However, since the images are the same, you could also just load the image once directly in the global scope, not in the method, and then use that image. Also, there's not much point in having that class just for that one method (more so since the instance is also garbage collected in the loop).
img = PhotoImage(file="pac_inizio.gif") # create once in global score
def create_image(): # regular function, not method
start_x = randint(1, canvas_width//2)
start_y = randint(1, canvas_height//2)
canvas.create_image(start_x, start_y, anchor=NW, image=img)

Related

Python: auto slideshow with PIL/TkInter with no saving images

I´m designing a slideshow with no user intervention, where:
a. Every image is generated by the Python script itself
b. There´s no file saving, for performance reasons
c. Every image is shown in fullscreen for a certain time
d. It´s a loop that´s supposed to never end. There´s always going to be an image to show
So far, by adapting code found in a few pages, I have it running. But every image is shown for X time and then the desktop background appears for a second or so.
I´d like to have a smooth switching from one file to next, such as FEH does. As a matter of fact, I´m trying to replace FEH because I need finer control of the display of each file (for instance, changing the time it appears on screen).
Here´s my code:
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from PIL import ImageTk
import tkinter
def show_center(pil_image, msDelay):
root = tkinter.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.attributes("-topmost", True)
canvas = tkinter.Canvas(root, width=w, height=h, highlightthickness=0)
canvas.pack()
canvas.configure(background='black')
image = ImageTk.PhotoImage(pil_image)
imagesprite = canvas.create_image(w / 2, h / 2, image=image)
root.after(msDelay, root.destroy)
root.mainloop()
### script body
while True:
# loads common background image
img = Image.open(baseImage)
draw = ImageDraw.Draw(img)
# here: image customization
draw.rectangle(....)
draw.text(....)
img.paste(....)
# shows this file
thisDelay = some Number Calculation
show_center(img, thisDelay)
Any ideas on how to avoid the desktop appearing between images? This will run in a headless Raspberry. I´m using Python3 on Raspbian.
Thanks in advance!
You can use after() instead of the while loop and simply use Label instead of Canvas:
import tkinter as tk
from PIL import Image, ImageTk, ImageDraw, ImageFont
import time
import random
def update_image():
# sample drawing
image = base_image.copy()
draw = ImageDraw.Draw(image)
draw.rectangle((100, 100, 500, 400), outline=random.choice(('red', 'green', 'blue', 'magenta', 'gold', 'orange')))
draw.text((120, 120), f"""{time.strftime("%F %T")}""")
# update image
tkimg = ImageTk.PhotoImage(image)
label.config(image=tkimg)
label.image = tkimg # save a reference to avoid garbage collected
ms_delay = random.randint(1000, 9000) # sample delay calculation
root.after(ms_delay, update_image)
root = tk.Tk()
root.attributes("-fullscreen", 1, "-topmost", 1)
base_image = Image.open("/path/to/base/image")
# label for showing image
label = tk.Label(root, bg="black")
label.pack(fill="both", expand=1)
update_image() # start the slide show
root.mainloop()
Well, it´s working quite well.
The solution required a bit of logic (maybe it makes sense not to destroy the object for every image) and a bit of good old trial & error.
The changes were:
Init the canvas only once, use global vars to make it persistent
For every image, call the display function and keep calling root.update() until the required timeout is reached
So, the prior function gets divided, and it looks like:
global canvas, root
global w, h
def init_image():
global canvas, root
global w, h
root = tkinter.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.attributes("-topmost", True)
canvas = tkinter.Canvas(root, width=w, height=h, highlightthickness=0)
canvas.pack()
canvas.configure(background='black')
return
def show_center(pil_image, msDelay):
global canvas, root
global w, h
image = ImageTk.PhotoImage(pil_image)
imagesprite = canvas.create_image(w / 2, h / 2, image=image)
inicio = int(time.time() * 1000)
while 1:
root.update()
if (int(time.time() * 1000) - inicio) > msDelay:
break
return
Function init_image() is called once at beginning, and function show_center() is called for every image as the original post.
I hope this can be useful to anybody trying to accomplish the same.

Roll up image implementation using canvas - Python

I want an image to scroll up inside a canvas. But I am unable to do it.
import tkinter
def scroll_image(event):
global roll
roll = roll - 10
canvas1.itemconfig(canvas1.create_image(20, roll, image=i))
roll = 10
windows = tkinter.Tk()
windows.title("My Application")
# Adding canvas to show image there
canvas1 = tkinter.Canvas(windows, width=200, height=100)
i = tkinter.PhotoImage(file="Capture.gif")
canvas1.create_image(20, 20, image=i)
# trying to implement roll-up image
canvas1.itemconfig(canvas1.create_image(20, 20, image=i))
canvas1.bind("<Configure>", scroll_image)
canvas1.grid(column=0, row=2)
windows.mainloop()
I tried using loops but I noticed that loops are running as expected but unfortunately the canvas update is taking place only once. So I removed the loop. But I need to find a way out to implement a roll up image.
Image is moving up and down.
This example use
module PIL/pillow to load jpg/png instead of gif
after(time_in_millisecond, function_name) to repeat function which moves image
img_id to use only one image (instead of creating many images with create_image)
canvas.move(ID, offset_x, offset_y) to move image (or other object)
canvas.coords(ID) to get current positon of image (or other object)
canvas.pack(fill='both', expand=True) to use full window. Canvas will use full window even when you resize window.
Code:
import tkinter as tk
from PIL import Image, ImageTk
def scroll_image():
global offset_y # inform function that you want to assign value to external variable instead of local one.
# move image
canvas.move(img_id, offset_x, offset_y)
# get current position
x, y = canvas.coords(img_id)
print(x, y)
# set position (if you don't use canvas.move)
#canvas.coords(img_id, x+offset_x, y+offset_y)
# x += offset_x
# y += offset_y
# change direction
if y <= -100 or y >= 0:
offset_y = -offset_y
# repeat after 20ms
root.after(20, scroll_image)
offset_x = 0
offset_y = -3
root = tk.Tk()
canvas = tk.Canvas(root)
canvas.pack(fill='both', expand=True) # use full window
#photo = tk.PhotoImage(file="Capture.gif")
image = Image.open("image.jpg")
photo = ImageTk.PhotoImage(image)
img_id = canvas.create_image(0, 0, image=photo)
# start scrolling
scroll_image()
root.mainloop()
You can use also canvas.coords(ID, x, y) to set new position.
More examples on GitHub for: Tkinter and other Python's modules

tkinter: move images with background

I'm looking for a way to move multiple images together with the background. Moving the background image works fine, but I can't figure out how to add two images on top (they disappear immediately) and then move together with the background. I guess there is an easy way to do that?
I appreciate any hint!
from tkinter import *
import time
tk = Tk()
w = 1000
h = 800
pos = 0
canvas = Canvas(tk, width=w, height=h)
canvas.pack()
tk.update()
background_image = PhotoImage(file="bg.gif")
background_label = Label(tk, image=background_image)
background_label.place(x=0, y=0)
tk.update()
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.place(x=files[f][0],y=files[f][1])
tk.update()
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-25)
tk.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
move(100)
tk.mainloop()
I'm having difficulty in understanding your code. Why create a canvas and then not use it? You have also littered your code with tk.update(), most of which are unnecessary. But, the described problem is because you create the labels inside a function and the association between label and image gets garbage collected when the function exits. You have to explicitly remember this association:
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.image = image # Lets the label remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
If you are then going to move these labels you might want to keep some kind of reference to them or you won't be able to address them.
Complete example
I changed tk to root because tk is the name usually used as alias for tkinter (eg. import tkinter as tk) which gets confusing.
I'm creating a image_list to hold references to the labels containing images. Later I use the list to loop through the labels and move them.
After I have built the GUI I wait 1000 milliseconds before starting the move function. Also I move the images just 1 pixel at a time to clearer see the action.
from tkinter import *
import time
root = Tk()
root.geometry('800x600') # Setting window size instead of usin canvas to do that
pos = 0
background_image = PhotoImage(file="bg.gif")
background_label = Label(root, image=background_image)
background_label.place(x=0, y=0)
image_list = [] # List for holding references to labels with images
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(root, image=image)
label.image = image # Remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
image_list.append(label) # Append created Label to the list
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-1)
for image in image_list: # Loop over labels in list
image.place(x=image.winfo_x()-1) # Move the label
root.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
root.after(1000, move, 100) # Waits 1 second and then moves images
root.mainloop()
By the way; after is a function much preferred over sleep as sleep suspends the program until its finished, whereas after works by making a call after a time and the program runs meanwhile. But if you are ok with that the program freezes during the move, then why not.

Resizing Image and fit it to the Canvas size

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.

Cropping and displaying images using PIL and Tkinter

I am beginning to learn Python after being trapped using VB6 forever. To help myself learn some gui with Tkinter, I'm making a simple slot machine. To animate the wheels, I have a ~300x2000 image that has all of the pictures I intend to use displayed as a strip. About 6 pictures right now. I just show part of the image (wheel) at a time to make it spin. Anyway, I'm having trouble with the part of the code where I come to the end of the image and need to transition from the end of the image back to the beginning (wheel spins down, so the start is the bottom and end is the top).
For some reason, I can't get the images to crop and display correctly, even though I think my script makes sense logically. My code is posted below. You can make an image around the resolution of 300x2000 or so to use with this to see the problem I am having. My code starts printing outside of the area that I want it to show (outside the showsize variable) and I can't figure out why.
Any help with this problem would be much appreciated. It seems that the crops don't cut the images short enough, but all the information that I found about it make me think my script should be working just fine. I've tried to annotate my code to explain what's going on.
from Tkinter import *
from PIL import Image, ImageTk
def main():
root = Tk()
root.title = ("Slot Machine")
canvas = Canvas(root, width=1500, height=800)
canvas.pack()
im = Image.open("colors.png")
wheelw = im.size[0] #width of source image
wheelh = im.size[1] #height of source image
showsize = 400 #amount of source image to show at a time - part of 'wheel' you can see
speed = 3 #spin speed of wheel
bx1 = 250 #Box 1 x - where the box will appear on the canvas
by = 250 #box 1 y
numberofspins = 100 #spin a few times through before stopping
cycle_period = 0 #amount of pause between each frame
for spintimes in range(1,numberofspins):
for y in range(wheelh,showsize,-speed): #spin to end of image, from bottom to top
cropped = im.crop((0, y-showsize, wheelw, y)) #crop which part of wheel is seen
tk_im = ImageTk.PhotoImage(cropped)
canvas.create_image(bx1, by, image=tk_im) #display image
canvas.update() # This refreshes the drawing on the canvas.
canvas.after(cycle_period) # This makes execution pause
for y in range (speed,showsize,speed): #add 2nd image to make spin loop
cropped1 = im.crop((0, 0, wheelw, showsize-y)) #img crop 1
cropped2 = im.crop((0, wheelh - y, wheelw, wheelh)) #img crop 2
tk_im1 = tk_im2 = None
tk_im1 = ImageTk.PhotoImage(cropped1)
tk_im2 = ImageTk.PhotoImage(cropped2)
#canvas.delete(ALL)
canvas.create_image(bx1, by, image=tk_im2) ###THIS IS WHERE THE PROBLEM IS..
canvas.create_image(bx1, by + y, image=tk_im1) ###PROBLEM
#For some reason these 2 lines are overdrawing where they should be. as y increases, the cropped img size should decrease, but doesn't
canvas.update() # This refreshes the drawing on the canvas
canvas.after(cycle_period) # This makes execution pause
root.mainloop()
if __name__ == '__main__':
main()
It's simpler, and much faster and smoother if you don't recreate your images on every cycle. Here's my solution using canvas.move(). Notice that I moved the canvas.create_image calls outside of the for loop. I also put the code in a class and added a 'spin' button, and added something to make it exit without errors.
from Tkinter import *
from PIL import Image, ImageTk
import sys
class SlotMachine():
def __init__(self):
root = Tk()
root.title = ("Slot Machine")
self.canvas = Canvas(root, width=1200, height=800)
self.canvas.grid(column=0,row=0)
button = Button(root, text="Spin!", width=20, command = self.spin)
button.grid(column=0,row=1)
self.alive = True
root.protocol("WM_DELETE_WINDOW", self.die)
root.mainloop()
def spin(self):
im = Image.open("colors.png")
wheelw = im.size[0] #width of source image
wheelh = im.size[1] #height of source image
showsize = 400 # amount of source image to show at a time -
# part of 'wheel' you can see
speed = 3 #spin speed of wheel
bx1 = 250 #Box 1 x - where the box will appear on the canvas
by = 250 #box 1 y
numberofspins = 100 #spin a few times through before stopping
cycle_period = 3 #amount of pause between each frame
tk_im1 = ImageTk.PhotoImage(im)
tk_im2 = ImageTk.PhotoImage(im)
im1id = self.canvas.create_image(bx1, by + showsize, image=tk_im1)
im2id = self.canvas.create_image(bx1, by + showsize + wheelh,
image=tk_im2)
for spintimes in range(1,numberofspins):
for y in range(wheelh,0,-speed):
if self.alive:
self.canvas.move(im1id, 0, -speed)
self.canvas.move(im2id, 0, -speed)
self.canvas.update()
self.canvas.after(cycle_period)
else:
sys.exit()
self.canvas.move(im1id, 0, wheelh)
self.canvas.move(im2id, 0, wheelh)
def die(self):
self.alive = False
if __name__ == '__main__':
mySlotMachine = SlotMachine()
This will place parts of the wheel outside of its box, but you can just put your slot machine texture on top of that.

Categories

Resources