Python tkinter displaying images as movie stream
<p>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?</p>

<pre><code>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()
</code></pre>

<p>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 <code>after</code> to schedule the image capture.</p>

<pre><code>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()
</code></pre>

<p>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.</p>

<h2>EDIT</h2>

<p>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.</p>

<pre><code>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())
</code></pre>

python tkinter pillow