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.
Related
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.
Hi i'm trying to create a class constructor to handle images in tkinter, since i'll be using many images in my code so I need a way to use images without using so much line of code.
I keep getting the error:
return _modes[mode]
KeyError: 'picture.png'
Here's the code:
from tkinter import *
import tkinter
from random import randint
from PIL import Image
from PIL import ImageTk
root = tkinter.Tk()
root.geometry('700x700')
class PhotoHandler:
def __init__(self,imagepath):
self.imagepath = imagepath
self.image = Image.open(self.imagepath)
self.image = ImageTk.PhotoImage(self.imagepath)
def returnn(self):
return self.image
search = PhotoHandler('picture.png').returnn()
root.mainloop()
You passed a string to the tkinter image. It expects a PIL image or mode. Since you passed a string, it thinks you're using an invalid mode. Pass the image instead:
class PhotoHandler:
def __init__(self,imagepath):
self.imagepath = imagepath
self.image = Image.open(self.imagepath)
self.image = ImageTk.PhotoImage(self.image)
def returnn(self):
return self.image
search = PhotoHandler('picture.png').returnn()
# add label for background image
background_label = tkinter.Label(root, image=search)
background_label.place(x=0, y=0, relwidth=1, relheight=1)
root.mainloop()
Here's the documentation:
https://pillow.readthedocs.io/en/4.2.x/reference/ImageTk.html
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()
I am trying to screen-grab and display the image quickly like a recording. It seems to all function well except the display window is "blinking" occasionally with a white frame. It doesn't appear to be every update or every other frame, but rather every 5 or so. Any thoughts on the cause?
from tkinter import *
from PIL import Image, ImageGrab, ImageTk
import threading
from collections import deque
from io import BytesIO
class buildFrame:
def __init__(self):
self.root = Tk()
self.land = Canvas(self.root, width=800, height=600)
self.land.pack()
self.genObj()
self.thsObj = self.land.create_image(0,0, anchor='nw', image=self.imgObj)
self.sStream = deque()
self.spinning = True
prQ = threading.Thread(target=self.procQ)
prQ.start()
t1 = threading.Thread(target=self.snapS, args=[100])
t1.start()
def genObj(self):
tmp = Image.new('RGBA', (800, 600), color=(0, 0, 0))
self.imgObj = ImageTk.PhotoImage(image=tmp)
def procQ(self):
while self.spinning == True:
if self.sStream:
self.land.itemconfig(self.thsObj, image=self.sStream[0])
self.sStream.popleft()
def snapS(self, shtCount):
quality_val = 70
for i in range(shtCount):
mem_file = BytesIO()
ImageGrab.grab().save(mem_file, format="JPEG", quality=quality_val)
mem_file.seek(0)
tmp = Image.open(mem_file)
tmp.thumbnail([800, 600])
img = ImageTk.PhotoImage(tmp)
self.sStream.append(img)
mem_file.close()
world = buildFrame()
world.root.mainloop()
You should avoid making Tk calls on non GUI threads. This works much more smoothly if you get rid of the threads entirely and use after to schedule the image capture.
from tkinter import *
from PIL import Image, ImageGrab, ImageTk
from io import BytesIO
class buildFrame:
def __init__(self):
self.root = Tk()
self.land = Canvas(self.root, width=800, height=600)
self.land.pack()
tmp = Image.new('RGBA', (800, 600), color=(0, 0, 0))
self.imgObj = ImageTk.PhotoImage(image=tmp)
self.thsObj = self.land.create_image(0,0, anchor='nw', image=self.imgObj)
self.root.after("idle", self.snapS)
def snapS(self):
quality_val = 70
mem_file = BytesIO()
ImageGrab.grab().save(mem_file, format="JPEG", quality=quality_val)
mem_file.seek(0)
tmp = Image.open(mem_file)
tmp.thumbnail([800, 600])
self.image = ImageTk.PhotoImage(tmp)
self.land.itemconfig(self.thsObj, image=self.image)
mem_file.close()
self.root.after(10, self.snapS)
world = buildFrame()
world.root.mainloop()
If you really want to use threads, you should queue the image stream and have the UI thread deserialize the tkinter image from the stream and display it. So one thread for capture and the main thread doing display.
EDIT
The following version keeps using a thread for the capture and passes the data via the deque but ensures that only the Tk UI thread operates on Tk objects. This needs some work to avoid accumulating images in the queue but a delay of 100ms between images works fine for now.
from tkinter import *
from PIL import Image, ImageGrab, ImageTk
import sys, threading, time
from collections import deque
from io import BytesIO
class buildFrame:
def __init__(self):
self.root = Tk()
self.root.wm_protocol("WM_DELETE_WINDOW", self.on_destroy)
self.land = Canvas(self.root, width=800, height=600)
self.land.pack()
self.genObj()
self.thsObj = self.land.create_image(0,0, anchor='nw', image=self.imgObj)
self.sStream = deque()
self.image_ready = threading.Event()
self.spinning = True
self.prQ = threading.Thread(target=self.procQ)
self.prQ.start()
self.t1 = threading.Thread(target=self.snapS, args=[100])
self.t1.start()
def on_destroy(self):
self.spinning = False
self.root.after_cancel(self.afterid)
self.prQ.join()
self.t1.join()
self.root.destroy()
def genObj(self):
tmp = Image.new('RGBA', (800, 600), color=(0, 0, 0))
self.imgObj = ImageTk.PhotoImage(image=tmp)
def procQ(self):
while self.spinning == True:
if self.image_ready.wait(0.1):
print(len(self.sStream))
self.image_ready.clear()
self.afterid = self.root.after(1, self.show_image)
def show_image(self):
stream = self.sStream[0]
self.sStream.popleft()
tmp = Image.open(stream)
tmp.thumbnail([800, 600])
self.image = ImageTk.PhotoImage(tmp)
self.land.itemconfig(self.thsObj, image=self.image)
stream.close()
def snapS(self, shtCount):
quality_val = 70
while self.spinning:
mem_file = BytesIO()
ImageGrab.grab().save(mem_file, format="JPEG", quality=quality_val)
mem_file.seek(0)
self.sStream.append(mem_file)
self.image_ready.set()
time.sleep(0.1)
def main():
world = buildFrame()
world.root.mainloop()
return 0
if __name__ == '__main__':
sys.exit(main())