Fade between images on screen using Python TKinter / imageTK - python

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()

Related

How to show Swipe transition with tkinter?

I'm creating a project where the Swipe transition between two images. I'm using the tkinter library and I'm stuck.
Šobrīd fragments:
import tkinter as tk
from PIL import ImageTk, Image
root = tk.Tk()
image1 = Image.open('name.jpg')
image2 = Image.open('name2.jpg')
width = min(image1.width, image2.width)
height = min(image1.height, image2.height)
image1 = image1.resize((width, height),Image.ANTIALIAS)
image2 = image2.resize((width, height),Image.ANTIALIAS)
def Swipe(image1, image2, end):
new_image = Image.new('RGB', (image1.width, image1.height), color='white')
for y in range(image1.height):
for x in range(image1.width):
if y <= end: new_image[x][y] = image1[x][y]
else: new_image[x][y] = image2[x][y]
return new_image
for i in range(image1.height):
got = Swipe(image1, image2, i)
But I get a error /'Image' object is not subscriptable/ And how do I realize this transition in the root window? Maybe someone could help?

How to remove the white lines on the sides of Tkinter?

enter image description here
As you can see in the screenshot, my program has white lines on the left and top, how can I remove them? I would like them not to be there at all.
import ctypes as ct
from tkinter import *
def dark_title_bar(window):
window.update()
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
set_window_attribute = ct.windll.dwmapi.DwmSetWindowAttribute
get_parent = ct.windll.user32.GetParent
hwnd = get_parent(window.winfo_id())
rendering_policy = DWMWA_USE_IMMERSIVE_DARK_MODE
value = 2
value = ct.c_int(value)
set_window_attribute(hwnd, rendering_policy, ct.byref(value), ct.sizeof(value))
win = Tk()
background_menu = PhotoImage(file="background-menu.png")
canvas_authorization = Canvas(win, width=1280, height=720)
canvas_authorization.grid()
canvas_authorization.create_image(640, 360, image=background_menu)
dark_title_bar(win)
win.geometry("1280x720")
win.geometry(str(win.winfo_width()+1) + "x" + str(win.winfo_height()+1))
#Returns to original size
win.geometry(str(win.winfo_width()-1) + "x" + str(win.winfo_height()-1))
win.geometry("1280x720")
win.mainloop()

Python ImageTk.PhotoImage slow loading of png with alphachannel

I wrote a little programm to load and show images.
Everything worked fine until i get to
the png file in the example (shade.png).
It takes 7 seven seconds to show this little pic.
Is there a bug in PhotoImage or did i miss some
parameter?
Here is my example code:
from PIL import Image, ImageTk
from six.moves import tkinter
import time
start = time.process_time()
print(time.process_time() - start)
root = tkinter.Tk()
img = Image.open('shade.png')
img = img.convert("RGBA") # make sure, it has alphachannel
print(time.process_time() - start, "after image.open")
img = ImageTk.PhotoImage(img)
print(time.process_time() - start, "after PhotoImage")
On my research, i found out that this
is an old known bug in Tk() especially in Tk_PhotoPutBlock.
https://core.tcl-lang.org/tk/tktview/b9827ece14da1cd186c5816dc45abc29cf9b8a9c
As i found a workaround that helps in some cases,
i want to post it here.
For further testing
and bug hunting.
import time # only to stop the time
from PIL import Image, ImageTk, ImageDraw
from six.moves import tkinter
HAVE_PIC = "no" # "yes" # or let create a pic if you have none
root = tkinter.Tk()
def masking_1(image): # faster
mask = image.copy()
mask.putalpha(1)
mask.paste(image, (0, 0), image)
# img.paste(img,(0, 0), mask)
image = mask.copy()
return image
def masking_2(image): # better quality
datas = image.getdata()
newData = []
for item in datas:
if item[3] == 0:
newData.append((item[0], item[1], item[2], 1))
else:
newData.append(item)
image.putdata(newData)
return image
def image_draw(): # creates the test pic
width = 300 # the bigger the slower
height = 300
colour = "green" # "#519ae7"
image = Image.new('RGBA', (width, height))
imd = ImageDraw.Draw(image, 'RGBA')
y = 0
while y < height:
x = y % 2
while x < width:
imd.point((x, y), colour)
x += 2
y += 1
return image
if HAVE_PIC == "yes":
img = Image.open('shade.png') # insert your problem pic
img = img.convert("RGBA") # make sure, it has alphachannel
else:
img = image_draw() # creates test pic
start = time.process_time()
print(time.process_time() - start, "after image.open")
# img.show() # to show that it is the same image
photo_image = ImageTk.PhotoImage(img, master=root) # too slow
print(time.process_time() - start, "after PhotoImage")
start = time.process_time()
print(time.process_time() - start, "before masking_1")
pic = masking_1(img)
# pic.show()
photo_image = ImageTk.PhotoImage(pic, master=root)
print(time.process_time() - start, "after masking_1")
start = time.process_time()
print(time.process_time() - start, "before masking_2")
pic = masking_2(img)
# pic.show()
photo_image = ImageTk.PhotoImage(pic, master=root)
print(time.process_time() - start, "after masking_2")
Output on my PC:
0.0 after image.open
9.734375 after PhotoImage
0.0 before masking_1
0.0 after masking_1
0.0 before masking_2
0.03125 after masking_2

Resizing and Redrawing Canvas in tkinter

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()

Sawtooth tkinter mainloop frame duration?

Trying to animate a sequence of PIL images using tkinter. The graph of my frame durations (ms) looks like this:
Anyone have any idea what could be causing this spiky sawtooth pattern?
Here's a script to reproduce:
from PIL import Image, ImageTk
import Tkinter
import time
import sys
def generate_frames(n):
"""
keep n under 101 * 101
"""
out = []
last_pil = None
for i in range(n):
if last_pil:
pil_image = last_pil.copy()
else:
pil_image = Image.new('L', (101, 101), 255)
x = i / 101
y = i % 101
pil_image.load()[x, y] = 0
out.append(ImageTk.PhotoImage(pil_image))
last_pil = pil_image
return out
def draw():
FRAME_COUNT =5000
master = Tkinter.Tk()
w = Tkinter.Canvas(master, width=302, height=302)
w.create_rectangle(49, 49, 252, 252)
w.pack()
frames = generate_frames(FRAME_COUNT)
def draw_frame(f, canvas_image):
print repr(time.time())
frame = frames[f]
if canvas_image is None:
canvas_image = w.create_image((151, 151), image=frame, anchor='center')
else:
w.itemconfigure(canvas_image, image=frame)
w.current_frame = frame # save a reference
next_frame = f + 1
if next_frame < FRAME_COUNT:
master.after(1, draw_frame, next_frame, canvas_image)
else:
sys.exit(0)
master.after(10, draw_frame, 0, None)
master.mainloop()
draw()
To see the plot, pipe output through
import sys
last = None
for line in sys.stdin:
value = float(line.strip()) * 1000
if last is None:
pass
else:
print (value - last)
last = value
then through
from matplotlib import pyplot
import sys
X = []
Y = []
for index, line in enumerate(sys.stdin):
line = line.strip()
X.append(index)
Y.append(float(line))
pyplot.plot(X, Y, '-')
pyplot.show()
Making it multi-threaded doesn't help:
class AnimationThread(threading.Thread):
FRAME_COUNT = 5000
def __init__(self, canvas):
threading.Thread.__init__(self)
self.canvas = canvas
self.frames = generate_frames(self.FRAME_COUNT)
def run(self):
w = self.canvas
frames = self.frames
canvas_image = None
for i in range(self.FRAME_COUNT):
print repr(time.time())
frame = frames[i]
if canvas_image is None:
canvas_image = w.create_image((151, 151), image=frame, anchor='center')
else:
w.itemconfigure(canvas_image, image=frame)
w.current_frame = frame
time.sleep(1 * .001)
def draw_threaded():
FRAME_COUNT = 5000
master = Tkinter.Tk()
w = Tkinter.Canvas(master, width=302, height=302)
w.create_rectangle(49, 49, 252, 252)
w.pack()
animation_thread = AnimationThread(w)
animation_thread.start()
master.mainloop()
animation_thread.join()
draw_threaded()
That closely resembles this kind of interference pattern when competing 60 and 50 Hz samples mingle:
(Original Wolfram|Alpha plot)
This is likely caused by having two things at different (but close) refresh rates. It's the same type of thing that happens when you try to film a TV screen and it looks like a black bar keeps moving down the image, or when car wheels appear to rotate backwards around their axles in car commercials. It is essentially an extension of the Moiré Effect.
I don't know whether or not it is caused by video drivers and/or hardware, but it is almost certainly caused by interfering cyclical patterns. It looks a lot like it should be the GC cycle interfering with your for loop (hence the sudden drop in the sawtooth-like wave as memory is freed up and can be allocated)

Categories

Resources