1

I've looked into embedding a pygame window inside a tkinter window (Reference: Embed Pygame in Tkinter)

I wanted to use this to embed a snapshot (once that works possibly a livefeed) made by the pygame.camera module

In the comments it is said the code should work with Linux (running on raspbian) when commenting out os.environ['SDL_VIDEODRIVER'] = 'windib'

However I can't get the embedding to work nor making a snapshot with pygame, and I can't figure out what's causing the issue. Here's the code I wrote:

import pygame as pg
import pygame.camera
import tkinter as tk
import os
import threading as th
tk.Frame = tk.LabelFrame

def start():
    root = tk.Tk()
    run = Viewer(root)
    return run

class Viewer(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.screen_width = parent.winfo_screenwidth()
        self.screen_height = parent.winfo_screenheight()
        self.startup(self.screen_width, self.screen_height)
    def startup(self, width, height):
        self.parent.protocol('WM_DELETE_WINDOW', self.parent.destroy)
        Viewer.embed = tk.Frame(self.parent, width=650, height=490)
        Viewer.embed.pack(side = tk.LEFT)
        self.buttonFrame = tk.Frame(self.parent, width=100, height=490)
        self.buttonFrame.pack(side = tk.RIGHT)
        self.refresh = tk.Button(self.buttonFrame,
                                 text='Refresh',
                                 command=self.refresh)
        self.refresh.pack()
    def refresh(self):
        self.c = Capture()
        self.c.snap()


class Capture(Viewer):
    def __init__(self):
        os.environ['SDL_WINDOWID'] = str(Viewer.embed.winfo_id())
        self.size = (640,480)
        self.display = pg.display.set_mode(self.size)
        self.clist = pg.camera.list_cameras()
        if not self.clist:
            raise ValueError("Sorry, no cameras detected.")
        self.cam = pg.camera.Camera(self.clist[0], self.size)
        self.cam.start()
        self.snapshot = pg.surface.Surface(self.size, 0, self.display)

        self.event = th.Thread(target=self.eventCatcher)
        self.event.start()
    def snap(self):
        self.snapshot = self.cam.get_image(self.snapshot)
    def eventCatcher(self):
        closed = False
        while not closed:
            events = pg.event.get()
            for e in events:
                if e.type == pg.QUIT:
                    self.cam.stop()
                    closed = True

pg.init()
pg.camera.init()
main = start()
main.mainloop()

This is what it looks like

HackXIt
  • 422
  • 5
  • 17
  • inside `Viewer` you have method `refresh(self)` and button `self.refresh = Button()` - for python it means that `Button` replaces method. – furas Nov 23 '17 at 16:09
  • maybe some code will not work in embed frame - start to removing lines of code till you get working example. – furas Nov 23 '17 at 16:38

1 Answers1

1

You have to use pygame after you set os.environ['SDL_WINDOWID']

os.environ['SDL_WINDOWID'] = str(...)
pygame.init()

EDIT: it works on Linux Mint 18.2

import pygame as pg
import pygame.camera
import tkinter as tk
import os
import threading as th

#tk.Frame = tk.LabelFrame

class Viewer(tk.Frame):

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent # there is self.master which keeps parent

        self.parent.protocol('WM_DELETE_WINDOW', self.parent.destroy)

        self.screen_width = parent.winfo_screenwidth()
        self.screen_height = parent.winfo_screenheight()

        self.embed = tk.Frame(self.parent, width=650, height=490)
        self.embed.pack(side='left')

        self.buttonFrame = tk.Frame(self.parent, width=100, height=490)
        self.buttonFrame.pack(side='right')

        self.parent.update() # need it to get embed.winfo_id() in Capture

        self.c = Capture(self)

        self.refreshButton = tk.Button(self.buttonFrame,
                                 text='Refresh',
                                 command=self.refresh)
        self.refreshButton.pack()

    def refresh(self):
        self.c.snap()


class Capture():

    def __init__(self, parent):
        os.environ['SDL_WINDOWID'] = str(parent.embed.winfo_id())

        pg.display.init()
        pg.camera.init()

        self.size = (640,480)
        self.display = pg.display.set_mode(self.size)
        self.display.fill(pg.Color(255,255,255))

        pg.display.update()

        self.clist = pg.camera.list_cameras()

        if not self.clist:
            raise ValueError("Sorry, no cameras detected.")
        print('cameras:', self.clist)

        self.cam = pg.camera.Camera(self.clist[0], self.size)
        self.cam.start()

        self.snapshot = pg.surface.Surface(self.size, 0, self.display)

        self.event = th.Thread(target=self.eventCatcher)
        self.event.start()

    def snap(self):
        print('snap ready:', self.cam.query_image())
        self.cam.get_image(self.snapshot)
        self.display.blit(self.snapshot, self.snapshot.get_rect())
        pg.display.update()

    def eventCatcher(self):
        closed = False
        while not closed:
            events = pg.event.get()
            for e in events:
                if e.type == pg.QUIT:
                    self.cam.stop()
                    closed = True

root = tk.Tk()
run = Viewer(root)
root.mainloop()
furas
  • 134,197
  • 12
  • 106
  • 148
  • Thanks! I'll test this tomorrow.. Also thanks for pointing out that `self.refresh` bug. – HackXIt Nov 23 '17 at 22:53
  • I'm getting a kernel error (using spyder3 IDE with it's inbuilt IPython console) when running it this way. The camera detection works because I'm getting exceptions when no cameras are detected. **EDIT:** Okay sorry, I missed another part of the code that you changed in the `snap()` method. Now it works. Very great help :) – HackXIt Nov 24 '17 at 14:30
  • I should add comments in code when I was changing it - but I forgot ;) – furas Nov 24 '17 at 15:25