Animated GIF with Python Turtle - python

I've found a way to display an animated GIF using Python tkinter and I'm trying to make it work with Python Turtle Graphics.
I can get the animated gif to display with the following code, but there are issues.
1) For some reason having .grid(row=0, column=0) puts the image off-screen.
2) The image has a surrounding border which I don't want to display.
I've tried .place() with various arguments, but none those place the image on the screen.
Any suggestions on how to place the image a a specific position without a border please?
# main file
import turtle
import tkinter_gif
screen = turtle.Screen()
canvas = screen.getcanvas()
gif_window = tkinter_gif.ImageLabel(canvas)
gif_window.grid(row=1, column=0)
gif_window.load("giphy.gif")
turtle.done()
# tkinter_gif.py
import tkinter as tk
from PIL import Image, ImageTk
from itertools import count
class ImageLabel(tk.Label):
"""a label that displays images, and plays them if they are gifs"""
def load(self, im):
if isinstance(im, str):
im = Image.open(im)
self.loc = 0
self.frames = []
try:
for i in count(1):
self.frames.append(ImageTk.PhotoImage(im.copy()))
im.seek(i)
except EOFError:
pass
try:
self.delay = im.info['duration']
except:
self.delay = 100
if len(self.frames) == 1:
self.config(image=self.frames[0])
else:
self.next_frame()
def unload(self):
self.config(image=None)
self.frames = None
def next_frame(self):
if self.frames:
self.loc += 1
self.loc %= len(self.frames)
self.config(image=self.frames[self.loc])
self.after(self.delay, self.next_frame)

I suggest to change ImageLabel from tk.Label to image item of Canvas as below:
class ImageLabel:
def __init__(self, canvas):
self.canvas = canvas
def load(self, im, x=0, y=0):
# create a canvas image item
self.image = self.canvas.create_image(x, y, image=None)
self.canvas.tag_lower(self.image)
if isinstance(im, str):
im = Image.open(im)
self.frames = []
try:
for i in count(1):
self.frames.append(ImageTk.PhotoImage(im.copy()))
im.seek(i)
except EOFError:
pass
try:
self.delay = im.info['duration']
except:
self.delay = 100
num_frames = len(self.frames)
if num_frames == 1:
self.canvas.itemconfig(self.image, image=self.frames[0])
else:
self.next_frame(0, num_frames)
def unload(self):
self.canvas.delete(self.image)
self.frames = None
def next_frame(self, loc, total):
if self.frames:
self.canvas.itemconfig(self.image, image=self.frames[loc])
loc = (loc + 1) % total
self.canvas.after(self.delay, self.next_frame, loc, total)
Then load it at specific position:
gif_window = tkinter_gif.ImageLabel(canvas)
gif_window.load("giphy.gif", -200, -200) # 0, 0 is the center of canvas

Related

Trying to make image slideshow with Tkinter (Python3)

I was trying to make an image slideshow program with Tkinter and Python3. No errors, but not showing the images that are inside my chosen directory. The other libraries that I have use are: PIL, random and glob. Your help will be greatly appreciated.
My system:
Ubuntu 20.04 LTS
Here is the code:
import tkinter as Tk
from PIL import Image, ImageTk
import random
import glob
class gui:
def __init__(self, mainwin):
self.counter = 0
self.mainwin = mainwin
self.mainwin.title("Our Photos")
self.colour()
self.mainwin.configure(bg = "yellow")
self.Frame = Tk.Frame(mainwin)
self.img = Tk.Label(self.Frame)
self.Frame.place(relheight = 0.85, relwidth = 0.9, relx = 0.05, rely = 0.05 )
self.img.pack()
self.pic()
def colour(self):
self.colours =['gray47','gray48']
c = random.choice(self.colours)
self.mainwin.configure(bg = c)
root.after(4000, self.colour)
def pic(self):
for name in glob.glob(r"/home/maheswar/Pictures/*"):
self.pic_list = []
val = name
self.pic_list.append(val)
if self.counter == len(self.pic_list) - 1:
self.counter = 0
else:
self.counter == self.counter + 1
self.file = self.pic_list[self.counter]
self.load = Image.open(self.file)
self.pic_width = self.load.size[0]
self.pic_height = self.load.size[1]
self.real_aspect = self.pic_width/self.pic_height
self.calc_width = int(self.real_aspect * 800)
self.load2 = self.load.resize((self.calc_width, 800))
self.render = ImageTk.PhotoImage(self.load2)
self.img.config(image = self.render)
self.img.image = self.render
root.after(2000, self.pic)
root = Tk.Tk()
myprog = gui(root)
root.geometry("1000x1000")
root.mainloop()
I found two mistaces - which probably you could see if you would use print() to debug code
First: you create list self.pic_list = [] inside loop so you replace previous content and this way you can get only one list. But you don't event need this loop but directly
self.pic_list = glob.glob(r"/home/maheswar/Pictures/*")
Second: you need = instead of == in line self.counter = self.counter + 1 or even simpler
self.counter += 1
Full working code with small changes.
import tkinter as Tk
from PIL import Image, ImageTk
import random
import glob
class GUI: # PEP8: `CamelCaseNames` for classes
def __init__(self, mainwin):
self.mainwin = mainwin
self.mainwin.title("Our Photos")
self.mainwin.configure(bg="yellow") # PEP8: inside `()` use `=` without spaces
self.counter = 0
self.frame = Tk.Frame(mainwin) # PEP8: `lower_case_names` for variables
self.frame.place(relheight=0.85, relwidth=0.9, relx=0.05, rely=0.05)
self.img = Tk.Label(self.frame)
self.img.pack()
self.pic_list = glob.glob("/home/maheswar/Pictures/*") # no need prefix `r`
self.colours = ['gray47', 'gray48'] # PEP8: space after `,`
self.colour()
self.pic()
def colour(self):
selected = random.choice(self.colours)
self.mainwin.configure(bg=selected)
root.after(4000, self.colour)
def pic(self):
filename = self.pic_list[self.counter]
image = Image.open(filename)
real_aspect = image.size[0]/image.size[1]
width = int(real_aspect * 800)
image = image.resize((width, 800))
self.photo = ImageTk.PhotoImage(image)
self.img.config(image=self.photo)
#self.img.image = self.render no need if you use `self.` to keep PhotoImage
self.counter += 1
if self.counter >= len(self.pic_list):
self.counter = 0
root.after(2000, self.pic)
# --- main ---
root = Tk.Tk()
myprog = GUI(root)
root.geometry("1000x1000")
root.mainloop()
PEP 8 -- Style Guide for Python Code

Display an animated gif in parallel to a Tkinter process, how?

I searched all around the web and the StackOverflow website, but didn't find any suitable answer to my problem.
I have a Python class used to create gifs objects:
import tkinter as tk
from PIL import ImageTk, Image
from itertools import count
class ImageLabel(tk.Label):
def load(self, im):
if isinstance(im, str):
im = Image.open(im)
self.loc = 0
self.frames = []
try:
for i in count(1):
self.frames.append(ImageTk.PhotoImage(im.copy()))
im.seek(i)
except EOFError:
pass
try:
self.delay = im.info['duration']
except:
self.delay = 100
if len(self.frames) == 1:
self.config(image=self.frames[0])
else:
self.next_frame()
def unload(self):
self.config(image="")
self.frames = None
def next_frame(self):
if self.frames:
self.loc += 1
self.loc %= len(self.frames)
self.config(image=self.frames[self.loc])
self.after(self.delay, self.next_frame)
Which can be used as follows (supposing we defined a frame before):
gif = ImageLabel( frame )
gif.load( "path/to/spinner.gif" )
gif.place( anchor="center", relx= 0.7, rely=0.5 )
I would like to be able to run this created gif in parallel with another command, in the same frame. For example: let's say I am clicking a button which performs a long operation, in this case I would like to have a gif displayed among it, which runs parallely and be destroyed after the process finishes.
Can you help me? Thanks.
I solved by simply placing a gif and sending in parallel a function to execute the job:
self.spinner_gif.place( anchor = "center", relx = 0.7, rely = 0.55 )
threading.Thread( target = self.Function ).start()
and then at the end of the Function function:
self.spinner_gif.place_forget()

Creating list of images in tkinter

I have a working code where tkinter application fetches a GIF file and displays it on clicking a button
It is working fine for a single GIF image.
What I want is to have an additional button for Next image and when I click on it, it should display next image. How can I create list of images?
from tkinter import *
from PIL import Image, ImageTk
class MyLabel(Label):
def __init__(self, master, filename):
im = Image.open(filename)
seq = []
try:
while 1:
seq.append(im.copy())
im.seek(len(seq)) # skip to next frame
except EOFError:
pass # we're done
try:
self.delay = im.info['duration']
except KeyError:
self.delay = 100
first = seq[0].convert('RGBA')
self.frames = [ImageTk.PhotoImage(first)]
Label.__init__(self, master, image=self.frames[0])
temp = seq[0]
for image in seq[1:]:
temp.paste(image)
frame = temp.convert('RGBA')
self.frames.append(ImageTk.PhotoImage(frame))
self.idx = 0
self.cancel = self.after(self.delay, self.play)
self.button = Button(text="Zoom out",command=self.play)
self.button.place(relx=0.4, rely=0.5, anchor=CENTER)
def play(self):
self.config(image=self.frames[self.idx])
self.idx += 1
if self.idx == len(self.frames):
self.idx = 0
self.cancel = self.after(self.delay, self.play)
root = Tk()
def stop_it():
anim.after_cancel(anim.cancel)
anim = MyLabel(root,'A.gif')
anim.pack()
stop_it()
root.mainloop()
My idea is if I can use below sort of code:
images = ['A.gif','B.gif']
images = iter(images)
img = next(images)
But how I can implement this in my current code?
Probably there are many ways for making a list of images , but I would make it like this :
# images
img1 = ImageTk.PhotoImage(Image.open("a.gif"))
img2 = ImageTk.PhotoImage(Image.open("b.gif"))
img3 = ImageTk.PhotoImage(Image.open("c.gif"))
# a list of images
Image_list = [img1 , img2 , img3 ]
For iterating I would make something like a counter
counter = len(Image_list)
img_panel = Label(root , image = Image_list[counter] )
img_panel.pack()
def next_image():
counter-=1
img_panel = Label(root , image = Image_list[counter] )
img_panel.pack()
next_image = Button(root , text= "next image" , command =next_image)
next_image.pack()

Padding issue while drawing points over QGraphicsScene

I have a PyQt application where I have drawn points using QPainter over a QGraphicsScene and made a drag n drop sort of a thing.
Now, there is one issue which I'm facing and that is I'm unable to drag those points at the extreme corner and edges of QGraphicsScene. It always seems as if some amount of padding or space is left.
How do I get round this problem?
Code:
from collections import deque
from datetime import datetime
import sys
from threading import Thread
import time
import numpy as np
import cv2
from PyQt4 import QtCore, QtGui
class CameraWidget(QtGui.QGraphicsView):
"""Independent camera feed
Uses threading to grab IP camera frames in the background
#param width - Width of the video frame
#param height - Height of the video frame
#param stream_link - IP/RTSP/Webcam link
#param aspect_ratio - Whether to maintain frame aspect ratio or force into fraame
"""
def __init__(self, width, height, stream_link=0, aspect_ratio=False, parent=None, deque_size=1):
super(CameraWidget, self).__init__(parent)
# Initialize deque used to store frames read from the stream
self.deque = deque(maxlen=deque_size)
self.screen_width = width
self.screen_height = height
self.maintain_aspect_ratio = aspect_ratio
self.camera_stream_link = stream_link
# Flag to check if camera is valid/working
self.online = False
self.capture = None
self.setScene(QtGui.QGraphicsScene(self))
self._pixmap_item = self.scene().addPixmap(QtGui.QPixmap())
canvas = Canvas()
lay = QtGui.QVBoxLayout()
lay.addWidget(canvas)
self.setLayout(lay)
self.load_network_stream()
# Start background frame grabbing
self.get_frame_thread = Thread(target=self.get_frame, args=())
self.get_frame_thread.daemon = True
self.get_frame_thread.start()
# Periodically set video frame to display
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.set_frame)
self.timer.start(0.5)
print("Started camera: {}".format(self.camera_stream_link))
def load_network_stream(self):
"""Verifies stream link and open new stream if valid"""
def load_network_stream_thread():
if self.verify_network_stream(self.camera_stream_link):
self.capture = cv2.VideoCapture(self.camera_stream_link)
self.online = True
self.load_stream_thread = Thread(target=load_network_stream_thread, args=())
self.load_stream_thread.daemon = True
self.load_stream_thread.start()
def verify_network_stream(self, link):
"""Attempts to receive a frame from given link"""
cap = cv2.VideoCapture(link)
if not cap.isOpened():
return False
cap.release()
return True
def get_frame(self):
"""Reads frame, resizes, and converts image to pixmap"""
while True:
try:
if self.capture.isOpened() and self.online:
# Read next frame from stream and insert into deque
status, frame = self.capture.read()
if status:
self.deque.append(frame)
else:
self.capture.release()
self.online = False
else:
# Attempt to reconnect
print("attempting to reconnect", self.camera_stream_link)
self.load_network_stream()
self.spin(2)
self.spin(0.001)
except AttributeError:
pass
def spin(self, seconds):
"""Pause for set amount of seconds, replaces time.sleep so program doesnt stall"""
time_end = time.time() + seconds
while time.time() < time_end:
QtGui.QApplication.processEvents()
def set_frame(self):
"""Sets pixmap image to video frame"""
if not self.online:
self.spin(1)
return
if self.deque and self.online:
# Grab latest frame
frame = self.deque[-1]
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = frame.shape
bytesPerLine = ch * w
# Convert to pixmap and set to video frame
image = QtGui.QImage(frame, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
pixmap = QtGui.QPixmap.fromImage(image.copy())
self._pixmap_item.setPixmap(pixmap)
self.fix_size()
def resizeEvent(self, event):
self.fix_size()
super().resizeEvent(event)
def fix_size(self):
self.fitInView(
self._pixmap_item,
QtCore.Qt.KeepAspectRatio
if self.maintain_aspect_ratio
else QtCore.Qt.IgnoreAspectRatio,
)
class Window(QtGui.QWidget):
def __init__(self, cam=None, parent=None):
super(Window, self).__init__(parent)
self.showMaximized()
self.screen_width = self.width()
self.screen_height = self.height()
# Create camera widget
print("Creating Camera Widget...")
self.camera = CameraWidget(self.screen_width, self.screen_height, cam)
lay = QtGui.QVBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
lay.setSpacing(0)
lay.addWidget(self.camera)
class Canvas(QtGui.QWidget):
DELTA = 200 #for the minimum distance
def __init__(self, parent=None):
super(Canvas, self).__init__(parent)
self.draggin_idx = -1
self.points = np.array([[x[0],x[1]] for x in [[100,200], [200,200], [100,400], [200,400]]], dtype=np.float)
self.id = None
self.points_dict = {}
for i, x in enumerate(self.points):
point=(int(x[0]),int(x[1]))
self.points_dict[i] = point
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawPoints(qp)
self.drawLines(qp)
qp.end()
def drawPoints(self, qp):
pen = QtGui.QPen()
pen.setWidth(10)
pen.setColor(QtGui.QColor('red'))
qp.setPen(pen)
for x,y in self.points:
qp.drawPoint(x,y)
def drawLines(self, qp):
qp.setPen(QtCore.Qt.red)
qp.drawLine(self.points_dict[0][0], self.points_dict[0][1], self.points_dict[1][0], self.points_dict[1][1])
qp.drawLine(self.points_dict[1][0], self.points_dict[1][1], self.points_dict[3][0], self.points_dict[3][1])
qp.drawLine(self.points_dict[3][0], self.points_dict[3][1], self.points_dict[2][0], self.points_dict[2][1])
qp.drawLine(self.points_dict[2][0], self.points_dict[2][1], self.points_dict[0][0], self.points_dict[0][1])
def _get_point(self, evt):
pos = evt.pos()
if pos.x() < 0:
pos.setX(0)
elif pos.x() > self.width():
pos.setX(self.width())
if pos.y() < 0:
pos.setY(0)
elif pos.y() > self.height():
pos.setY(self.height())
return np.array([pos.x(), pos.y()])
#get the click coordinates
def mousePressEvent(self, evt):
if evt.button() == QtCore.Qt.LeftButton and self.draggin_idx == -1:
point = self._get_point(evt)
int_point = (int(point[0]), int(point[1]))
min_dist = ((int_point[0]-self.points_dict[0][0])**2 + (int_point[1]-self.points_dict[0][1])**2)**0.5
for i, x in enumerate(list(self.points_dict.values())):
distance = ((int_point[0]-x[0])**2 + (int_point[1]-x[1])**2)**0.5
if min_dist >= distance:
min_dist = distance
self.id = i
#dist will hold the square distance from the click to the points
dist = self.points - point
dist = dist[:,0]**2 + dist[:,1]**2
dist[dist>self.DELTA] = np.inf #obviate the distances above DELTA
if dist.min() < np.inf:
self.draggin_idx = dist.argmin()
def mouseMoveEvent(self, evt):
if self.draggin_idx != -1:
point = self._get_point(evt)
self.points[self.draggin_idx] = point
self.update()
def mouseReleaseEvent(self, evt):
if evt.button() == QtCore.Qt.LeftButton and self.draggin_idx != -1:
point = self._get_point(evt)
int_point = (int(point[0]), int(point[1]))
self.points_dict[self.id] = int_point
self.points[self.draggin_idx] = point
self.draggin_idx = -1
self.update()
camera = 0
if __name__ == "__main__":
app = QtGui.QApplication([])
win = Window(camera)
sys.exit(app.exec_())
Edit:
I've one more requirement.
The mousePressEvent and mouseReleaseEvent in my Canvas class gives me coordinates w.r.t. my monitor resolution, instead I want it w.r.t. QGraphicsView. Say e.g. my screen_resolution is 1920x1080 and the size of my QGraphicsView is 640x480 then I should get points in accordance with 640x480.
The simplest solution would be to add lay.setContentsMargins(0, 0, 0, 0) for the layout of the graphics view:
class CameraWidget(QtGui.QGraphicsView):
def __init__(self, width, height, stream_link=0, aspect_ratio=False, parent=None, deque_size=1):
# ...
canvas = Canvas()
lay = QtGui.QVBoxLayout()
lay.addWidget(canvas)
self.setLayout(lay)
lay.setContentsMargins(0, 0, 0, 0)
# ...
But consider that doing all this is not suggested.
First of all, you don't need a layout for a single widget, as you could just create the widget with the view as a parent and then resize it in the resizeEvent:
# ...
self.canvas = Canvas(self)
def resizeEvent(self, event):
self.fix_size()
super().resizeEvent(event)
self.canvas.resize(self.size())
Widgets like QGraphicsView should not have a layout set, it's unsupported and may lead to unwanted behavior or even bugs under certain conditions.
In any case, it doesn't make a lot of sense to add a widget on top of a QGraphicsView if that widget is used for painting and mouse interaction: QGraphicsView already provides better implementation for that by using QGraphicsRectItem or QGraphicsLineItem.
And, even if it weren't the case, custom drawing over a graphics view should be done in its drawForeground() implementation.

How to update an image repeatedly in tkinter

This is a function that creates an image:
def footgraph(self):
load = Image.open('centertext_out.png')
load= load.resize((500, 500), Image.ANTIALIAS)
render = ImageTk.PhotoImage(load)
self.img = Label( image=render)
self.img.image = render
self.img.place(x=150, y=5)
self.scale = tk.Scale(self.win, variable=self.value, orient="horizontal",length = 200,
from_=self.df['index'].unique().min(), to=self.df['index'].unique().max(), resolution =1,command=self.updateScaleFoot)
self.scale.place(x=250, y = 500)
self.stop.pack(expand='true',fill='both')
self.stop.place(x=200, y =500)
self.play.pack(expand='true',fill='both')
self.play.place(x=150, y = 500)
Here is a code for play button that keeps updating image:
while True:
self.index += 1
#update image(centertext_out.png) and save
load = Image.open('centertext_out.png')
load= load.resize((500, 500), Image.ANTIALIAS)
render = ImageTk.PhotoImage(load)
img = Label( image=render)
img.image = render
img.place(x=150, y=5)
time.sleep(10)
This loop is working fine. Self.index keeps updating. But image is not updating and screen hangs.
Edit:
When I use slider new image gets appended to previous one like this :
Edit:
I have narrowed down the problem. Below is the code for play function. When I click bar graph and if for bar graph turns true then code runs smoothly but it doesnt seem to be working for the second statement even if I have nothing inside of it.
def startanimation(self):
self.pause = 0
print("pause"+ str(self.pause))
while True:
if self.pause == 1:
break
self.index = self.index +1
print ("scale is now %s" % (self.index))
if "bar" in self.graphtype:
#some code
#draw canvas
self.fig.canvas.draw()
self.fig.canvas.flush_events()
time.sleep(0.2)
if "foot" in self.graphtype:
print("inside")
time.sleep(0.2)
Edit:
Changed code according to one of the answers.
class Application:
def __init__(self, master):
self.win = master
self.geo = self.win.geometry
self.geo("800x800+400+400")
self.win['bg'] = "black"
####################################################some code
def startanimation(self):
self.pause = 0
print("pause"+ str(self.pause))
if "foot" in self.graphtype:
self.win.after(1,self.test)
def test(self):
print("hi")
self.pause = 0
guide = pd.read_csv("guide.csv")
print("hey")
self.index +=1
test is called only once. hey is printed only once
Solved: check this link
Python Tkinter after() Only Executing Once

Categories

Resources