Sawtooth tkinter mainloop frame duration? - python

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)

Related

Tkinter canvas image transition

I want to have some transition to show the images in the canvas, I'm using Tkinter and I'm looping through the images to show them on the canvas but I need to have some transition while switching among the images.
I'm using canvasName.create_image method for showing the images. Need a way to show them smoothly.
Here is my code:
def Multi_view_rotate():
window.geometry(str(scr_w)+"x"+str(scr_h)+"+0+0")
z_out = 20
global timeSleep
timeSleepVal = int(timeSleep.get())
global footerPath
footerPath = footerPath.get()
#geting director from entry boxes
global portDirEntry
portDirEntry = portDirEntry.get()
global colorEntry
bgcolor = colorEntry.get()
allPaths = getPaths(portDirEntry)
#directory = r"C:\Users\DotNet\Desktop\Ragazinana Data reduced\diashow\4 Random\Landschaft"
#Get paths
pathsPrt = allPaths[0]
pathsLand = allPaths[1]
#read the image
#call the function to get the picture object with new size
global numOfImagesPort
global numOfImagesLand
#footer path
#footerPath = "C:/Users/DotNet/Desktop/Ragazinana Data reduced/diashow/ragaziana_s.jpg"
#Footer will take 8% of the screen width
per_w_footer = cal_per_num(8, scr_w)
# Footer Image operations
canvasFoot = Canvas(window,width=per_w_footer, height=scr_h, bg=bgcolor, highlightthickness=1, highlightbackground=bgcolor)
canvasFoot.grid(row=0, column=0)
#footerImg = get_img_fit_size(footerPath, scr_h, per_w_footer, True)
footerImg1 = Image.open(footerPath)
footerImg2 = footerImg1.transpose(Image.ROTATE_270)
footerImg3 = footerImg2.resize((int(per_w_footer),int(scr_h)), Image.ANTIALIAS)
footerImg = ImageTk.PhotoImage(footerImg3)
footer = canvasFoot.create_image(per_w_footer/2,scr_h/2,anchor=CENTER, image=footerImg)
while(numOfImagesPort<=len(pathsPrt)-1 or numOfImagesLand<=len(pathsLand)-1 ):
pathPort = pathsPrt[numOfImagesPort]
#increase the index to get the next file in the next loop
numOfImagesPort=numOfImagesPort+1
#if the next photo is out of bound then assign it to the first index
if(numOfImagesPort >= len(pathsPrt)):# if total is 5 pic, 1st loop 0 > 6 /reset the loop
numOfImagesPort=0
# each image will take as following in percentage
per_w_imgs_portriate = cal_per_num(42, scr_w)
per_w_imgs_landscape= cal_per_num(50, scr_w)
#Create the canvases
canvasPort = Canvas(window,width=per_w_imgs_portriate, height=scr_h, bg=bgcolor, highlightthickness=10, highlightbackground=bgcolor)
#gird plays the canvas without it the canvas will not work
canvasPort.grid(row=0, column=1)
#in order to make the picture fit in the rotated state in the half of the screen
# we make the get_img_fit_size adjust it to us to that size by providing
# screen hight as a width and half of the screen with as a height
imgPort = get_img_fit_size(pathPort, scr_h, per_w_imgs_landscape, True)
portImgCanvas = canvasPort.create_image(int(scr_w/4.3),int(scr_h/2),anchor=CENTER, image=imgPort)**
window.update()
time.sleep(timeSleepVal/2)
# Landscape image
pathLand = pathsLand[numOfImagesLand]
numOfImagesLand = numOfImagesLand+1
if(numOfImagesLand >= len(pathsLand)):
numOfImagesLand=0
canvasLand = Canvas(window,width=per_w_imgs_landscape, height=scr_h, bg=bgcolor, highlightthickness=10, highlightbackground=bgcolor)
canvasLand.grid(row=0, column=2)
imgLand = get_img_fit_size(pathLand, scr_h, per_w_imgs_portriate, True)
landImgCanvas = canvasLand.create_image(int(scr_w/4.5),int(scr_h/2),anchor=CENTER, image=imgLand)
window.update()
time.sleep(timeSleepVal/2)
window.mainloop()
I don't think there is something like this built into Tkinter.PhotoImage, but you could manually create a "fade" transition by randomly selecting pixels and setting them to the color values of the next image:
import tkinter, random
root = tkinter.Tk()
c = tkinter.Canvas(root, width=800, height=400)
c.pack()
img_a = tkinter.PhotoImage(file="a.gif")
img_b = tkinter.PhotoImage(file="b.gif")
i = c.create_image(0, 0, image=img_a, anchor="nw")
pixels = [(x, y) for x in range(img_a.width()) for y in range(img_a.height())]
random.shuffle(pixels)
def fade(n=1000):
global pixels, i
for _ in range(min(n, len(pixels))):
x, y = pixels.pop()
col = "#%02x%02x%02x" % img_b.get(x,y)
img_a.put(col, (x, y))
c.delete(i)
i = c.create_image(0, 0, image=img_a, anchor="nw")
if pixels:
c.after(1, fade)
fade()
root.mainloop()
This is slow, though. The after with 1 ms is only to keep the UI from freezing (don't use while with time.sleep in Tkinter!). For a smoother transition, instead of replacing pixel values you might gradually shift all pixels towards the values in the next image, but that will be even slower since you'd change all pixels in each step.
Instead of pure tkinter, we can try it wit PIL and numpy, but it is not noticeably faster, and least not the way I did it:
import numpy as np
from PIL import Image, ImageTk
from itertools import islice
...
arr_a = np.array(Image.open("a.gif").convert("RGB"))
arr_b = np.array(Image.open("b.gif").convert("RGB"))
img = ImageTk.PhotoImage(Image.fromarray(arr_a, mode="RGB"))
i = c.create_image(0, 0, image=img, anchor="nw")
h, w, _ = arr_a.shape
pixels = [(x, y) for x in range(w) for y in range(h)]
random.shuffle(pixels)
def fade(k=0, n=1000):
global i, img
X, Y = zip(*islice(pixels, k, k+n))
arr_a[Y,X] = arr_b[Y,X]
c.delete(i)
img = ImageTk.PhotoImage(Image.fromarray(arr_a, mode="RGB"))
i = c.create_image(0, 0, image=img, anchor="nw")
if k + n < w * h:
c.after(1, fade, k+n, n)
fade()
root.mainloop()
However, this also allows us to replace entire lines at once. The effect is not quite as nice, but it is much faster (also note changed n and if condition).
...
h, w, _ = arr_a.shape
lines = list(range(h))
random.shuffle(lines)
def fade(k=0, n=10):
global i, img
Y = lines[k:k+n]
arr_a[Y] = arr_b[Y]
...
if k + n < h:
c.after(1, fade, k+n, n)
...
This can also easily be transformed to a vertical or horizontal slide transition by simply not shuffling the lines (for columns, use arr_a[:,X] = arr_b[:,X]).

tkinter: Mesh distance is innacurate

I am trying to draw a simple mesh with tkinter:
from tkinter import *
root=Tk()
root.title('Mesh simulator')
window_identifiers = {}
frame_identifiers = {}
canvas_identifiers = {}
mesh_identifiers = []
window_identifiers["main_window"] = root
SETTINGS_canvas_total_width = 2048
SETTINGS_canvas_total_height = 1536
SETTINGS_canvas_visible_width = 800
SETTINGS_canvas_visible_height = 600
SETTINGS_canvas_color = "black"
SETTINGS_grid_color = "white"
mesh_density = 50
frame=Frame(window_identifiers["main_window"],width=SETTINGS_canvas_visible_width,height=SETTINGS_canvas_visible_height)
frame_identifiers["body_holder"] = frame
frame.grid(row=0,column=0)
canvas=Canvas(frame,bd=-2, bg=SETTINGS_canvas_color,width=SETTINGS_canvas_visible_width,height=SETTINGS_canvas_visible_height,scrollregion=(0,0,SETTINGS_canvas_total_width,SETTINGS_canvas_total_height), highlightthickness=0)
canvas_identifiers["main_canvas"] = canvas
canvas.grid(row=0, column=0)
i = 0
while(i<=SETTINGS_canvas_total_height):
l = canvas_identifiers["main_canvas"].create_line(0, i, SETTINGS_canvas_total_width, i, width=1, fill=SETTINGS_grid_color)
mesh_identifiers.append(l)
i+=mesh_density
i = 0
while(i<=SETTINGS_canvas_total_width):
l = canvas_identifiers["main_canvas"].create_line(i, 0, i, SETTINGS_canvas_total_height, width=1, fill=SETTINGS_grid_color)
mesh_identifiers.append(l)
i+=mesh_density
root.mainloop()
But on the very end, when I measure up the distance between two lines, it seems it is not 50px, but about 62-64px. I don't have a clue what adds those 12 pixels per square. Can anyone explain me the root cause of this please, based on my upper snippet?
EDIT:
Interesting fact, I've just done the measurement on 2 different monitors (laptop and 22" one), and results are interesting. On 22" monitor, everything seems perfectly fine (image 1) while on laptop monitor, there's an offset (image 2)
And since this is not an HTML and web-design issue, I am even more confused now :)

Best way to implement a tkinter calibration bar?

I have a sensor that needs to be calibrated. The error depends on the orientation of the sensor and can be estimated and shown to the user. I would like to do this visually using tkinter for python 3.x.
The ideal result would be something like this with the black bar live updating depending on the live error:
How could I do this best in tkinter? I looked at the Scale and Progressbar widgets but they did not have the needed functionality.
I was thinking about showing the colorbar as an image and overlaying the black indicator bar and constantly updating the position of this black bar. Would this be possible?
I shall split up the answer in two parts. The first part solves the issue of live updating the data, by using two threads as suggested by #Martineau. The communication between the threads is done by a simple lock and a global variable.
The second part creates the calibration bar widget using the gradient calculation algorithm defined by #Martineau.
PART 1:
This example code shows a small window with one number. The number is generated in one thread and the GUI is shown by another thread.
import threading
import time
import copy
import tkinter as tk
import random
class ThreadCreateData(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
#Declaring data global allows to access it between threads
global data
# create data for the first time
data_original = self.create_data()
while True: # Go in the permanent loop
print('Data creator tries to get lock')
lock.acquire()
print('Data creator has it!')
data = copy.deepcopy(data_original)
print('Data creator is releasing it')
lock.release()
print('Data creator is creating data...')
data_original = self.create_data()
def create_data(self):
'''A function that returns a string representation of a number changing between one and ten.'''
a = random.randrange(1, 10)
time.sleep(1) #Simulating calculation time
return str(a)
class ThreadShowData(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
# Declaring data global allows to access it between threads
global data
root = tk.Tk()
root.geometry("200x150")
# creation of an instance
app = Window(root, lock)
# mainloop
root.mainloop()
# Here, we are creating our class, Window, and inheriting from the Frame
# class. Frame is a class from the tkinter module. (see Lib/tkinter/__init__)
class Window(tk.Frame):
# Define settings upon initialization. Here you can specify
def __init__(self, master=None,lock=None):
# parameters that you want to send through the Frame class.
tk.Frame.__init__(self, master)
# reference to the master widget, which is the tk window
self.master = master
#Execute function update_gui after 1ms
self.master.after(1, self.update_gui(lock))
def update_gui(self, lock):
global data
print('updating')
print('GUI trying to get lock')
lock.acquire()
print('GUI got the lock')
new_data = copy.deepcopy(data)
print('GUI releasing lock')
lock.release()
data_label = tk.Label(self.master, text=new_data)
data_label.grid(row=1, column=0)
print('GUI wating to update')
self.master.after(2000, lambda: self.update_gui(lock)) #run update_gui every 2 seconds
if __name__ == '__main__':
# creating the lock
lock = threading.Lock()
#Initializing data
data = None
#creating threads
a = ThreadCreateData("Data_creating_thread")
b = ThreadShowData("Data_showing_thread")
#starting threads
b.start()
a.start()
PART 2: Below the code for a simple calibration bar widget is shown. The bar only contains 5 ticks you can adapt the code to add more if wanted. Pay attention to the needed input formats. To test the widget a random value is generated and shown on the widget every 0.5s.
import tkinter as tk
from PIL import ImageTk, Image
import sys
EPSILON = sys.float_info.epsilon # Smallest possible difference.
###Functions to create the color bar (credits to Martineau)
def convert_to_rgb(minval, maxval, val, colors):
for index, color in enumerate(colors):
if color == 'YELLOW':
colors[index] = (255, 255, 0)
elif color == 'RED':
colors[index] = (255, 0, 0)
elif color == 'GREEN':
colors[index] = (0, 255, 0)
# "colors" is a series of RGB colors delineating a series of
# adjacent linear color gradients between each pair.
# Determine where the given value falls proportionality within
# the range from minval->maxval and scale that fractional value
# by the total number in the "colors" pallette.
i_f = float(val - minval) / float(maxval - minval) * (len(colors) - 1)
# Determine the lower index of the pair of color indices this
# value corresponds and its fractional distance between the lower
# and the upper colors.
i, f = int(i_f // 1), i_f % 1 # Split into whole & fractional parts.
# Does it fall exactly on one of the color points?
if f < EPSILON:
return colors[i]
else: # Otherwise return a color within the range between them.
(r1, g1, b1), (r2, g2, b2) = colors[i], colors[i + 1]
return int(r1 + f * (r2 - r1)), int(g1 + f * (g2 - g1)), int(b1 + f * (b2 - b1))
def create_gradient_img(size, colors):
''''Creates a gradient image based on size (1x2 tuple) and colors (1x3 tuple with strings as entries,
possible entries are GREEN RED and YELLOW)'''
img = Image.new('RGB', (size[0],size[1]), "black") # Create a new image
pixels = img.load() # Create the pixel map
for i in range(img.size[0]): # For every pixel:
for j in range(img.size[1]):
pixels[i,j] = convert_to_rgb(minval=0,maxval=size[0],val=i,colors=colors) # Set the colour accordingly
return img
### The widget
class CalibrationBar(tk.Frame):
""""The calibration bar widget. Takes as arguments the parent, the start value of the calibration bar, the
limits in the form of a 1x5 list these will form the ticks on the bar and the boolean two sided. In case it
is two sided the gradient will be double."""
def __init__(self, parent, limits, name, value=0, two_sided=False):
tk.Frame.__init__(self, parent)
#Assign attributes
self.value = value
self.limits = limits
self.two_sided = two_sided
self.name=name
#Test that the limits are 5 digits
assert len(limits)== 5 , 'There are 5 ticks so you should give me 5 values!'
#Create a canvas in which we are going to put the drawings
self.canvas_width = 400
self.canvas_height = 100
self.canvas = tk.Canvas(self,
width=self.canvas_width,
height=self.canvas_height)
#Create the color bar
self.bar_offset = int(0.05 * self.canvas_width)
self.bar_width = int(self.canvas_width*0.9)
self.bar_height = int(self.canvas_height*0.8)
if two_sided:
self.color_bar = ImageTk.PhotoImage(create_gradient_img([self.bar_width,self.bar_height],['RED','GREEN','RED']))
else:
self.color_bar = ImageTk.PhotoImage(create_gradient_img([self.bar_width,self.bar_height], ['GREEN', 'YELLOW', 'RED']))
#Put the colorbar on the canvas
self.canvas.create_image(self.bar_offset, 0, image=self.color_bar, anchor = tk.NW)
#Indicator line
self.indicator_line = self.create_indicator_line()
#Tick lines & values
for i in range(0,5):
print(str(limits[i]))
if i==4:
print('was dees')
self.canvas.create_line(self.bar_offset + int(self.bar_width - 2), int(self.canvas_height * 0.7),
self.bar_offset + int(self.bar_width - 2), int(self.canvas_height * 0.9), fill="#000000", width=3)
self.canvas.create_text(self.bar_offset + int(self.bar_width - 2), int(self.canvas_height * 0.9), text=str(limits[i]), anchor=tk.N)
else:
self.canvas.create_line(self.bar_offset + int(i * self.bar_width / 4), int(self.canvas_height * 0.7), self.bar_offset + int(i * self.bar_width / 4), int(self.canvas_height * 0.9), fill="#000000", width=3)
self.canvas.create_text(self.bar_offset + int(i * self.bar_width / 4), int(self.canvas_height * 0.9), text=str(limits[i]), anchor=tk.N)
#Text
self.label = tk.Label(text=self.name+': '+str(self.value),font=14)
#Positioning
self.canvas.grid(row=0,column=0,sticky=tk.N)
self.label.grid(row=1,column=0,sticky=tk.N)
def create_indicator_line(self):
""""Creates the indicator line"""
diff = self.value-self.limits[0]
ratio = diff/(self.limits[-1]-self.limits[0])
if diff<0:
ratio=0
elif ratio>1:
ratio=1
xpos = int(self.bar_offset+ratio*self.bar_width)
return self.canvas.create_line(xpos, 0, xpos, 0.9 * self.canvas_height, fill="#000000", width=3)
def update_value(self,value):
self.value = value
self.label.config(text = self.name+': '+str(self.value))
self.canvas.delete(self.indicator_line)
self.indicator_line = self.create_indicator_line()
###Creation of window to place the widget
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry('400x400')
self.calibration_bar = CalibrationBar(self, value= -5, limits=[-10, -5, 0, 5, 10], name='Inclination angle', two_sided=True)
self.calibration_bar.grid(column=0, row=4)
self.after(500,self.update_data)
def update_data(self):
""""Randomly assing values to the widget and update the widget."""
import random
a = random.randrange(-15, 15)
self.calibration_bar.update_value(a)
self.after(500, self.update_data)
###Calling our window
if __name__ == "__main__":
app=App()
app.mainloop()
This is how it looks like:
To get a live updating calibration bar you should just combine part one and two in your application.

Need help trying to get information to display correctly using graphics

In my program that I have posted below, the user is suppose to click on a circle that is drawn using a specific color. Depending on whether or not the circle is colored yellow or blue, text will display in the same window as "Correct" or "Incorrect". The problem I am having is after the user click on a circle the text will display, but after the first try, the text that is in the window will remain, leading to the unfortuate problem of each subsequent click causing the new text to write over the previously displayed text. If anyone knows how to get the text to "reset" or "clear" that is in the window, so the window will be blank each time the user clicks, I will appreciate it. Thanks
from graphics import *
import tkinter as tk
import threading
import random
class App():
def __init__(self):
self.win = GraphWin('Demo2', 800, 600) # give title and dimensions
self.th = threading.Thread(target=self.FlashThread, daemon=False)
def FlashThread(self):
while not self.win.isClosed():
count = random.randint(0, 8)
t = threading.Timer(1.0, self.flash, [count])
t.start()
t.join()
def flash(self, count):
try:
diameter = 50
centers = ((55,55), (170,55), (285,55), (55,170), (170,170),
(285,170), (55,285), (170,285), (285,285))
circles = list()
for point in centers:
c = Circle(Point(point[0], point[1]), diameter)
circles.append(c)
c.setFill("blue")
c.draw(self.win)
circles[count].setFill("yellow")
mouseClick = self.win.getMouse()
correctMessage = Text(Point(self.win.getWidth()/2, 20), 'Correct!')
incorrectMessage = Text(Point(self.win.getWidth()/2, 20), 'Incorrect,Try Again')
leftX = centers[count][0] - diameter
rightX = centers[count][0] + diameter
upperY = centers[count][1] - diameter
lowerY = centers[count][1] + diameter
if (upperY < mouseClick.y < lowerY) and (leftX < mouseClick.x < rightX):
correctMessage.draw(self.win)
else:
incorrectMessage.draw(self.win)
except:
self.win.exit(0)
if __name__ == "__main__":
try:
app = App()
app.th.start()
app.win.mainloop()
app.th.join()
finally:
app.th.close()
app.close()
You can do this, using undraw() method. Change your __init__ to:
def __init__(self):
self.win = GraphWin('Demo2', 800, 600) # give title and dimensions
self.th = threading.Thread(target=self.FlashThread)
self.correctMessage = Text(Point(self.win.getWidth()/2, 20), 'Correct!')
self.incorrectMessage = Text(Point(self.win.getWidth()/2, 20), 'Incorrect,Try Again')
and flash to
def flash(self, count):
try:
self.correctMessage.undraw()
self.incorrectMessage.undraw()
diameter = 50
centers = ((55,55), (170,55), (285,55), (55,170), (170,170),
(285,170), (55,285), (170,285), (285,285))
circles = list()
for point in centers:
c = Circle(Point(point[0], point[1]), diameter)
circles.append(c)
c.setFill("blue")
c.draw(self.win)
circles[count].setFill("yellow")
mouseClick = self.win.getMouse()
leftX = centers[count][0] - diameter
rightX = centers[count][0] + diameter
upperY = centers[count][1] - diameter
lowerY = centers[count][1] + diameter
if (upperY < mouseClick.y < lowerY) and (leftX < mouseClick.x < rightX):
self.correctMessage.draw(self.win)
else:
self.incorrectMessage.draw(self.win)
except:
self.win.exit(0)

Fade between images on screen using Python TKinter / imageTK

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

Categories

Resources