I want to make a class that has a picture and it is changed to the next one by mouse click.I'm new to oop, my idea here was to make class similar to real life where there is new class instance for every new picture, is it possible to do it this way? Here is my code
import tkinter as tk
from PIL import Image,ImageTk
class Picture():
_count=1
def __init__(self,window):
self.id=Picture._count
Picture._count+=1
self.img=Image.open(r'C:\ImgArchive\img%s.png' % self.id)
self.pimg = ImageTk.PhotoImage(self.img)
self.lab=tk.Label(window,image=self.pimg)
self.lab.pack()
self.lab.bind('<1>',self.click)
def click(self,event):
self.lab.destroy()
self=self.__init__(window)
window = tk.Tk()
window.title('Album')
window.geometry('1200x900')
pic=Picture(window)
window.mainloop()
It works fine, but i'm not sure that old instances of my class is deleted, are they? And i use self.lab.destroy() because if i dont new picture appears down, like this
instead of this
So why it happens?What is elegant way for it?
Below example produces a simple image viewer tested with path of C:\Users\Public\Pictures\Sample Pictures, let me know if anything's unclear:
import tkinter as tk
from PIL import Image, ImageTk
#required for getting files in a path
import os
class ImageViewer(tk.Label):
def __init__(self, master, path):
super().__init__(master)
self.path = path
self.image_index = 0
self.list_image_files()
self.show_image()
self.bind('<Button-1>', self.show_next_image)
def list_files(self):
(_, _, filenames) = next(os.walk(self.path))
return filenames
def list_image_files(self):
self.image_files = list()
for a_file in self.list_files():
if a_file.lower().endswith(('.jpg', '.png', '.jpeg')):
self.image_files.append(a_file)
def show_image(self):
img = Image.open(self.path + "\\" + self.image_files[self.image_index])
self.img = ImageTk.PhotoImage(img)
self['image'] = self.img
def show_next_image(self, *args):
self.image_index = (self.image_index + 1) % len(self.image_files)
self.show_image()
root = tk.Tk()
mypath = r"C:\Users\Public\Pictures\Sample Pictures"
a = ImageViewer(root, mypath)
a.pack()
root.mainloop()
Related
So I have a couple of images in a folder and I want to do a little pack opener on tkinter where if I press on a button it randomly opens an Image of that folder and shows it. So I did this:
import os
import random
from PIL import Image
from tkinter import *
def pack():
path ='C:\\Users\\matt\OneDrive\Images\cards'
files = os.listdir(path)
index = random.randrange(0, len(files))
image = Image.open(files[index])
image.show()
pack_button = Button(window,text = " Pack ",fg="white",bg = 'black',command = pack)
pack_button.grid(row = 2,column = 1,padx = 10,pady = 5)
window.mainloop()
The problem is that this function doesn't want to work and it always tells me:
AttributeError: type object 'Image' has no attribute 'open'
Can someone please help me? And does someone know how to do a button out of an image?
Thank you in advance.☺
Assuming you're using Python 3, and you have a folder named card images in the same directory as your Python script, and that folder contains .png images of your playing cards, then the following should work:
import tkinter as tk
def get_random_image_path():
from random import choice
from pathlib import Path
return str(choice(list(Path("card images").glob("*.png"))))
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Random card")
self.geometry("171x239")
self.resizable(width=False, height=False)
self.image = tk.PhotoImage(file=get_random_image_path())
self.button = tk.Button(self, image=self.image, command=self.assign_new_image)
self.button.pack()
def assign_new_image(self):
self.image = tk.PhotoImage(file=get_random_image_path())
self.button.configure(image=self.image)
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
Note: I just grabbed three public domain card images from Wikimedia Commons. You can download them either in .svg or .png format in different resolutions. In my case I downloaded them as .pngs with a resolution of 171x239, which is why I picked the same dimensions for the tkinter window. Since I've only downloaded three images, sometimes it appears as though clicking the button doesn't seem to do anything, which isn't the case - it's just that, with only three images to choose from, we're likely to pick the same image multiple times in a row.
To get your example running, see a minimal solution below.
For a ready Tkinter program and to avoid PIL use Paul M.'s solution.
import os
import random
from PIL import Image
# assuming that this dir contains only images
# otherwise test if r is an image
img_folder = r'/home/ktw/Desktop/test_img'
def pack():
r = random.choice(os.listdir(img_folder))
image = Image.open(os.path.join(img_folder, r))
image.show()
if __name__=='__main__':
pack()
EDIT:
This should work for you. Just change the path to the full path of your image folder.
choice(os.listdir(img_folder)) gives you only the name of the random file. os.path.join(img_folder, choice(os.listdir(img_folder))) builds an absolute path from the image folder and the random image so Tk.PhotoImage should work.
import tkinter as tk
import os
from random import choice
from pathlib import Path
img_folder = r'/home/ktw/Desktop/images'
def get_random_image_path():
return os.path.join(img_folder, choice(os.listdir(img_folder)))
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Random card")
self.geometry("171x239")
self.resizable(width=False, height=False)
self.image = tk.PhotoImage(file=get_random_image_path())
self.button = tk.Button(self, image=self.image, command=self.assign_new_image)
self.button.pack()
def assign_new_image(self):
self.image = tk.PhotoImage(file=get_random_image_path())
self.button.configure(image=self.image)
if __name__=='__main__':
application = Application()
application.mainloop()
import tkinter as tk
import os
from random import choice
from pathlib import Path
from PIL import Image
img_folder = r'C:\\Users\\matt\OneDrive\Images\cards'
def get_random_image_path():
return os.path.join(img_folder, choice(os.listdir(img_folder)))
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Random card")
self.resizable(width=False, height=False)
self.image = tk.PhotoImage(get_random_image_path())
self.button = tk.Button(self, image=self.image, command=self.assign_new_image)
self.button.pack()
def assign_new_image(self):
self.image = tk.PhotoImage(file=get_random_image_path())
self.button.configure(image=self.image)
if __name__=='__main__':
application = Application()
application.mainloop()
and it says this:
File "C:\Users\matt\OneDrive\Images\cards\pack opener.py", line 31, in <module>
application = Application()
File "C:\Users\matt\OneDrive\Images\cards\pack opener.py", line 22, in __init__
self.button = tk.Button(self, image=self.image, command=self.assign_new_image)
File "C:\Users\matt\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2679, in __init__
Widget.__init__(self, master, 'button', cnf, kw)
File "C:\Users\matt\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2601, in __init__
self.tk.call(
_tkinter.TclError: image "C:\\Users\\matt\OneDrive\Images\cards\Vettel.png" doesn't exist
I need the images to change if the cursor is inside the window without moving, but I managed to make the image changes only if the cursor is moving inside the window, how can I change the code?
from itertools import cycle
import tkinter as tk
from PIL import ImageTk, Image
import glob
image_files = glob.glob("*.jpg")
root = tk.Toplevel()
root.geometry("1600x900")
pictures = cycle((ImageTk.PhotoImage(file=image), image) for image in image_files)
picture_display = tk.Label(root)
picture_display.pack()
def show_slides(event):
img_object, img_name = next(pictures)
root.after(500, picture_display.config(image=img_object))
root.bind("<Motion>", show_slides)
root.mainloop()
You could bind the <Enter> and <Leave> events and use a flag to control the call, followed by using the after method to loop the function.
def show_slides(event=None):
global change_slide
img_object, img_name = next(pictures)
picture_display.config(image=img_object)
change_slide=root.after(500,show_slides)
def stop_slides(event):
root.after_cancel(change_slide)
root.bind("<Enter>", show_slides)
root.bind("<Leave>", stop_slides)
UPDATE
Using a flag might cause multiple calls being scheduled it the events happen during the 500ms delay, you can use after_cancel to terminate it.
You can calculate cursor position in loop and show image if it's located within tkinter window:
import glob
import tkinter as tk
from PIL import ImageTk
from itertools import cycle
class App(object):
def __init__(self):
self.root = tk.Tk()
self.root.geometry('900x600')
self.lbl = tk.Label(self.root)
self.lbl.pack()
files = glob.glob('*.jpg')
self.images = cycle((ImageTk.PhotoImage(file=f), f) for f in files)
self.show()
self.root.mainloop()
def show(self):
abs_coord_x = self.root.winfo_pointerx() - self.root.winfo_rootx()
abs_coord_y = self.root.winfo_pointery() - self.root.winfo_rooty()
if 0 <= abs_coord_x <= self.root.winfo_width() and 0 <= abs_coord_y <= self.root.winfo_height():
img_object, img_name = next(self.images)
self.lbl.config(image=img_object)
self.root.after(1000, self.show)
App()
I was just wondering, is there any way in tkinter in python to resize/rescale a picture with the mouse (similar to how you can rescale images in google docs)?
The code I have so far:
def addImage(self, docx):
filename = filedialog.askopenfile(title="Select a file")
myImage = ImageTk.PhotoImage(Image.open(filename.name))
position = docx.index(INSERT)
docx.image_create(position, image=myImage)
docx.photo = myImage
# here I want to be able to resize the image I have put in my document with the mouse
Thanks in advance
I have tried this
from tkinter import *
from PIL import Image,ImageTk
class Resizable:
def __init__(self,label,image):
self.label=label
self.image=image
self.label.master.bind('<Motion>',self.position)
self.flag=False
def position(self,event):
self.dimentions=(self.label.winfo_width(),self.label.winfo_height())
self.x,self.y = event.x,event.y
if (
self.x in range (self.dimentions[0]-5,self.dimentions[0],1) and
self.y in range (self.dimentions[1]-5,self.dimentions[1],1)
):
self.label.config(cursor='sizing')
self.label.master.bind('<ButtonRelease-1>',self.end)
self.label.bind('<Button-1>',self.start)
else:
self.label.config(cursor='')
self.label.unbind('<Button-1>')
def end(self,event):
self.flag=True
self.label.master.unbind('<ButtonRelease-1>')
def start(self,event):
self.flag=False
self.resize()
def resize(self):
if not self.flag:
self.label.config(cursor='sizing')
try:
self.photoimage=ImageTk.PhotoImage(self.image.resize((self.x,self.y),Image.ANTIALIAS))
except:
pass
self.label.config(image=self.photoimage)
self.label.update()
self.label.after(1,self.resize)
root=Tk()
root.geometry('400x300')
img_label=Label(bd=0)
img_label.pack()
image=Image.open('sample.png')
photoimage=ImageTk.PhotoImage(image.resize((100,100),Image.ANTIALIAS))
img_label.config(image=photoimage)
Resizable(img_label,image)
root.mainloop()
I believe you have a different way of inserting your image, but you can modify and improve this approach as per your requirement.
Summary of code purpose: change tk.Button['image'] to either selected file, or screen snip
I'm getting this error:
_tkinter.TclError: image "<PIL.Image.Image image mode=RGB size=600x400 at 0x34045F0>" doesn't exist
But I can't understand why it doesn't exist since if I useprint(self.image_selected) I get
<PIL.Image.Image image mode=RGB size=600x400 at 0x3D16650>
Does it need to be a global variable? Doesn't adding self take care of that?
CODE
import tkinter as tk
from tkinter import filedialog
from PIL import ImageGrab, ImageTk
class MCVE():
def __init__(self, master):
self.master = master
self.ButtonOne = tk.Button(text="start",width=30,height=30,command = lambda: self.GetImg(master))
self.ButtonOne.pack()
self.image_selected = None
def GetImg(self, master):
self.newWin = tk.Toplevel(self.master)
self.ButtonTwo = tk.Button(self.newWin,text="snip", command = lambda: self.Snip(self.master))
self.ButtonThree = tk.Button(self.newWin, text="open", command = lambda: self.FileO(self.master))
self.ButtonTwo.pack()
self.ButtonThree.pack()
def Snip(self, master):
self.image_selected = ImageGrab.grab(bbox=(0,0,600,400))
self.changeImg()
def FileO(self, master):
ret = filedialog.askopenfilename() # filedialog.askopenfilename(initialdir='/home/user/images/')
if ret:
self.image_selected = ImageTk.PhotoImage(file=ret)
self.changeImg()
def changeImg(self):
if self.image_selected:
print(self.image_selected)
#self.ButtonOne['image'] = self.image_selected
self.ButtonOne.config(image=self.image_selected)
def main():
root = tk.Tk()
MCVE(root)
root.mainloop()
if __name__ == '__main__':
main()
I tested the program, and as you stated, the 'snip' button gave me the error. I could fix it by changing the format of the image to a ImageTk.PhotoImage.
def Snip(self, master):
self.image_selected = ImageTk.PhotoImage(ImageGrab.grab(bbox=(0,0,600,400)))
self.changeImg()
I have to say, though, that the photo showed on the button icon after selecting the image was weird, but I guess it was because i tested it with the wrong resolution! ;)
I'm attempting to make a slideshow with Tkinter but I'm having trouble sizing the images. They only show as the default size while I'd like to make them all uniform. I can do it for individual images using Image.open and resize, but I can't sort out how to get it to work over an iteration. I'd appreciate the help:
import Tkinter as tk
from PIL import Image, ImageTk
from itertools import cycle
class App(tk.Tk):
def __init__(self, image_files, x, y, delay):
tk.Tk.__init__(self)
self.geometry('+{}+{}'.format(x,y))
self.delay = delay
self.pictures = cycle((ImageTk.PhotoImage(file=image), image) for image in image_files)
self.pictures = self.pictures
self.picture_display = tk.Label(self)
self.picture_display.pack()
def show_slides(self):
img_object, img_name = next(self.pictures)
self.picture_display.config(image=img_object)
self.title(img_name)
self.after(self.delay, self.show_slides)
def run(self):
self.mainloop()
delay = 3500
image_files = [
'c:/users/xxx/pictures/47487_10100692997065139_1074926086_n.jpg',
'E:\\1415\\20141216_105336.jpg'
]
x = 100
y = 50
app = App(image_files,x,y,delay)
app.show_slides()
app.run()
You were close, but not quite there yet. Thus, I changed your example to make it work:
import Tkinter as tk
from PIL import Image, ImageTk
from itertools import cycle
class App(tk.Tk):
def __init__(self, image_files, x, y, delay):
tk.Tk.__init__(self)
self.geometry('+{}+{}'.format(x,y))
self.delay = delay
#self.pictures = cycle((ImageTk.PhotoImage(file=image), image) for image in image_files)
self.pictures = cycle(image for image in image_files)
self.pictures = self.pictures
self.picture_display = tk.Label(self)
self.picture_display.pack()
self.images = [] # to keep references to images.
def show_slides(self):
img_name = next(self.pictures)
image_pil = Image.open(img_name).resize((300, 300)) #<-- resize images here
self.images.append(ImageTk.PhotoImage(image_pil))
self.picture_display.config(image=self.images[-1])
self.title(img_name)
self.after(self.delay, self.show_slides)
def run(self):
self.mainloop()
delay = 3500
image_files = [
'./empty.gif',
'./empty2.gif',
'./empty1.gif'
]
x = 200
y = 150
app = App(image_files,x,y,delay)
app.show_slides()
app.run()
Basicly, image resizing must be done using PIL image, before you make an instance of ImageTk.PhotoImage. In two critical points I made comments in the example, so you know where to look for. Hope this helps.