tkinter: move images with background - python

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.

Related

How to properly use buttons in tkinter with same window

This is my first day using tkinter, and I wanted to know how to properly code what I have done.
What I wanted to make was a kind of diaporama so I wanted to find a way to display pictures in a canvas and have two buttons so I could go to the previous picture or the following one. The way I stored pictures is using a list lof numpy arrays, with l of size n, so I have n pictures and they are the same size. So I wrote the following code:
from tkinter import *
import numpy as np
from PIL import Image, ImageTk
import sys
sys.setrecursionlimit(10000) #I increased the recursion limit because I had some issues
l = [(255*np.random.rand(50,50)).astype(np.uint8) for i in range(105)] #this is a random list in case you want to test the code
def next(i,n):
if i == n-1:
return 0
else:
return i+1
def previous(i,n):
if i==0:
return n-1
else:
return i-1
window = Tk()
window.geometry("200x100+900+500")
def display(i):
#This is to clear my window at each iteration because I would like to keep the same window
for widget in window.winfo_children():
widget.destroy()
array = l[i] #this is the i-th picture
img = ImageTk.PhotoImage(image=Image.fromarray(array),master = window)
canvas = Canvas(window, width=48, height=48)
canvas.place(x=10, y=20)
canvas.create_image(0,0, anchor="nw", image=img)
Label(window, text="Picture n°"+str(i), fg="black").place(x=5, y=0)
Button(window, text ='Next',command=lambda: display(next(i,len(l)))).place(x=140, y=35)
Button(window, text ='Previous',command = lambda: display(previous(i,len(l)))).place(x=70, y=35)
window.mainloop()
display(0)
I know that is bad code and not the way it should be written. It is working fine but I need help to improve the code please.
You should only put the code of updating the image inside display() and create the interface outside the function. Then recursion is not needed.
Also a global variable is required to keep track the current index to the image list. This variable should be updated when Next or Previous button is clicked.
Below is a modified example:
from tkinter import *
import numpy as np
from PIL import Image, ImageTk
l = [(255*np.random.rand(50,50)).astype(np.uint8) for i in range(105)] #this is a random list in case you want to test the code
current = 0
def display(dir):
global current
# update "current" based on "dir"
current = (current + dir) % len(l)
# show the "current" image
image = ImageTk.PhotoImage(Image.fromarray(l[current]))
imgbox.config(image=image, text=f"Picture n°{current}")
imgbox.image = image # keep a reference to avoid "image" from being garbage collected
window = Tk()
window.geometry("200x100+900+500")
# use a label for showing image and text together
imgbox = Label(window, fg="black", compound="bottom", width=70)
imgbox.place(x=5, y=0)
Button(window, text ='Next', command=lambda: display(+1)).place(x=150, y=35)
Button(window, text ='Previous', command=lambda: display(-1)).place(x=80, y=35)
display(0) # show first image
window.mainloop()

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

Display images in tkinter canvas

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)

PIL: Jump to next photo after one click event

I am preparing data for deep running. So I have to get certain pixel coordinates for each picture. Only one coordinate per photo is required.
So when I use PIL to input one click, I try to implement coordinates so that I can go to the next picture.
However, when I write the code as below, the coordinates are output in duplicate to only one picture, and the next picture does not appear on the screen.
How can I make sure that only one coordinate is recorded on a single picture?
from PIL import Image, ImageTk
import tkinter
import os
URL = './SavedImage/'
imgList = os.listdir(URL)
print(imgList)
width = 852
height = 480
stepW = 852/4
stepH = 480/5
def callback(event):
print("clicked at: ", event.x, event.y)
window = tkinter.Tk(className='pla')
for file in sorted(imgList):
a=True
image = Image.open(os.path.join(URL, file))
print(image)
canvas = tkinter.Canvas(window, width=image.size[0], height=image.size[1])
canvas.pack()
image_tk = ImageTk.PhotoImage(image)
canvas.create_image(image.size[0]//2, image.size[1]//2, image=image_tk)
canvas.bind("<Button-1>", callback)
tkinter.mainloop()
I am not 100% sure I understand what you need but it looks like to me you are trying to get one set of cord's for each image in a list of images.
I would do this by creating a function and a tracking variable to loop through each image on at a time and on click update a new list with the image and cord's then loop to next image.
Let me know if you have any questions.
Example:
from PIL import Image, ImageTk
import tkinter
import os
URL = './SavedImage/'
imgList = os.listdir(URL)
width = 852
height = 480
stepW = 852/4
stepH = 480/5
tracker = 0
list_images_with_cords = [] # added list for final results
def callback(event):
# Added global's.
global tracker,list_images_with_cords
# Used to append final results to list.
list_images_with_cords.append([imgList[tracker], event.x, event.y])
# This tracker lets us go through each item on the list.
tracker += 1
# After appending list go to next image.
open_next()
window = tkinter.Tk(className='pla')
# Creates just one canvas that we can update later.
canvas = tkinter.Canvas(window)
canvas.pack()
def open_next():
# Adding global's.
global image, canvas, image_tk, tracker
# Clearing canvas before drawing new image.
canvas.delete("all")
# Checking for valid index in list.
if tracker < len(imgList):
image = Image.open(os.path.join(URL, imgList[tracker]))
# use config() to update canvas.
canvas.config(width=image.size[0], height=image.size[1])
image_tk = ImageTk.PhotoImage(image)
canvas.create_image(image.size[0]//2, image.size[1]//2, image=image_tk)
canvas.bind("<Button-1>", callback)
else:
# This else statement is just for when we run out of images.
# It will display all the results in a textbox.
canvas.destroy()
txt = tkinter.Text(window, width=25)
txt.pack()
for item in list_images_with_cords:
txt.insert("end", "{}\n\n".format(item))
open_next()
tkinter.mainloop()

Trying to Load an Image with Dialogue Box in Python and Display it in a Layer - PYTHON 3.4 using Tkinter and PIL

I am trying to Make a simple Photo editor. I already got all the functions for editing photos. Just trying to make it look nice with a GUI. When the user presses file>open, the open dialogue box comes up and they can choose an image. Then I want that image to load inside of a Layer in the GUI.
Here is my code so far:
p.s. When I put the code that is inside the open_img function outise the function, it works, but when i put it inside the function, no image loads
import os
from tkinter import *
import tkinter.messagebox
from PIL import Image, ImageTk
from tkinter.filedialog import askopenfilename
def g_quit():
mExit=tkinter.messagebox.askyesno(title="Quit", message="Are You Sure?")
if mExit>0:
mGui.destroy()
return
#open menu
def open_img():
file = tkinter.filedialog.askopenfilename(initialdir='D:/Users/')
w_box = 500
h_box = 500
pil_image = Image.open(file)
w, h = pil_image.size
pil_image_resized = resize(w, h, w_box, h_box, pil_image)
# wr, hr = pil_image_resized.size
tk_image = ImageTk.PhotoImage(pil_image_resized)
label2.config(image=tk_image, width=w_box, height=h_box)
def resize(w, h, w_box, h_box, pil_image):
'''
resize a pil_image object so it will fit into
a box of size w_box times h_box, but retain aspect ratio
'''
f1 = 1.0*w_box/w # 1.0 forces float division in Python2
f2 = 1.0*h_box/h
factor = min([f1, f2])
#print(f1, f2, factor) # test
# use best down-sizing filter
width = int(w*factor)
height = int(h*factor)
return pil_image.resize((width, height), Image.ANTIALIAS)
mGui = Tk()
mGui.title('Photo Filters')
mGui.geometry('650x500')
mGui.resizable(0, 0) #Disable Resizeability
photoFrame = Frame(mGui, bg="orange", width=500, height=500)
photoFrame.pack(side=LEFT)
filtersFrame = Frame(mGui, bg="yellow", width=150, height=500)
filtersFrame.pack(side=LEFT, fill=Y)
label2 = Label(photoFrame)
#Create Buttons for All the Possible Filters
negative_btn = Button(filtersFrame, text="Negative")
negative_btn.pack()
weighted_grayscale_btn = Button(filtersFrame, text="weighted Grayscale")
weighted_grayscale_btn.pack()
solarize_btn = Button(filtersFrame, text="Solarize")
solarize_btn.pack()
black_and_white_btn = Button(filtersFrame, text="Black and White")
black_and_white_btn.pack()
black_and_white_and_gray_btn = Button(filtersFrame, text="Black and White and Gray")
black_and_white_and_gray_btn.pack()
extreme_contrast_btn = Button(filtersFrame, text="Extreme Contrast")
extreme_contrast_btn.pack()
sepia_tint_btn = Button(filtersFrame, text="Sepia Tint")
sepia_tint_btn.pack()
adjust_component_btn = Button(filtersFrame, text="Adjusts Components")
adjust_component_btn.pack()
posterize_btn = Button(filtersFrame, text="Posterize")
posterize_btn.pack()
simplify_btn = Button(filtersFrame, text="Simplify")
simplify_btn.pack()
detect_edges_btn = Button(filtersFrame, text="Detect Edges")
detect_edges_btn.pack()
detect_edges_better_btn = Button(filtersFrame, text="Detect Edges Better")
detect_edges_better_btn.pack()
blur_btn = Button(filtersFrame, text="Blur")
blur_btn.pack()
flip_image = Button(filtersFrame, text="Flip Horizontal")
flip_image.pack()
#Menu Bar
menubar = Menu(mGui)
filemenu = Menu(menubar)
#Create the Menu Options that go under drop down
filemenu.add_command(label="New")
filemenu.add_command(label="Open", command=open_img)
filemenu.add_command(label="Save As")
filemenu.add_command(label="Close", command=g_quit)
#Create the Main Button (e.g file) which contains the drop down options
menubar.add_cascade(label="File", menu=filemenu)
mGui.config(menu=menubar)
mGui.mainloop()
You've actually got two problems here. One of them, you found for yourself—you never call label2.pack(), so the label itself never gets displayed. But, even after you fix that, the label is blank.
As the Label documentation says:
You can use the label to display PhotoImage and BitmapImage objects. When doing this, make sure you keep a reference to the image object, to prevent it from being garbage collected by Python’s memory allocator.
You're not doing that. You store the PhotoImage in a local variable, tk_image, which goes away as soon as the function exits.
That should explain why it works if you move the code outside a function. If you do that, tk_image becomes a global variable, which doesn't go away until the whole program exits. And, since you're already using global variables all over the place implicitly, one possible fix is to use an explicit global for this:
def open_img():
global tk_image
# the rest of your code
However, a much better trick is to just cram the image into an attribute of something else that's going to stick around, like the Label object itself:
def open_img():
# your existing code
label2.tk_image = ImageTk.PhotoImage(pil_image_resized)
label2.config(image=label2.tk_image, width=w_box, height=h_box)
Now, the image can't be garbage collected until the label that's using it gets garbage collected (or until you replace it with a new image).
In fact, now that I look at the docs, this is exactly what the example of using a PhotoImage with a Label does:
photo = PhotoImage(file="icon.gif")
w = Label(parent, image=photo)
w.photo = photo
w.pack()

Categories

Resources