TclError: image "pyimage3" doesn't exist (works only on first run)

841 Views Asked by At

I'm trying to create a screenshot area tool which can be triggered from python code.

It works at first call but, on second call I get bellow error: TclError: image "pyimage2" doesn't exist

Only after I restart the kernel will work, but only once, then the same error appears..

I included all the code bellow. Has python-mss as a dependency (and tkinter on linux)

import os
from tkinter import Tk, Canvas, PhotoImage, YES, BOTH, NW, Toplevel 
from mss import mss, tools



def full_screenshot(fn="img"):

    # Primary monitor screenshot
    filename = os.path.relpath(f'{fn}.png')

    with mss() as sct:
        sct.shot(mon=1, output=filename)

    return filename


def area_screenshot(sx, sy, ex, ey, fn="img"):

    try:
        os.mkdir("snaps")
    except:
        pass

    with mss() as sct:
        # The screen part to capture
        monitor = {"top": sy, "left": sx, "width": ex-sx, "height": ey-sy, "mon":1}
        filename = os.path.relpath("./snaps/" + fn + "-{top}x{left}_{width}x{height}.png".format(**monitor))

    # Grab the data
    sct_img = sct.grab(monitor)

    # Save to the picture file
    tools.to_png(sct_img.rgb, sct_img.size, output=filename)

    del sct


    return filename




class _Snap:

    def __init__(self, name="snap"):
    
        self.name = name
        self.snap = None

        self.window = Tk()
        #self.window = Toplevel(root)
    
        self.window.title("Snip picture")
        self.window.attributes('-fullscreen', True)  
    
        self.c = Canvas(self.window, cursor="cross")
        self.c.pack(expand=YES, fill=BOTH)

        fname = full_screenshot("temp")

        bg = PhotoImage(file=fname)
        self.c.create_image(0, 0, image=bg, anchor=NW)

        self.window.bind("<Escape>", self.quit_fullscreen)
        self.c.bind("<ButtonPress-1>", self.on_press)
        self.c.bind("<B1-Motion>", self.on_drag)
        self.c.bind("<ButtonRelease-1>", self.on_release)

        self.window.mainloop()
        #root.mainloop()

    
    def quit_fullscreen(self, event):
        self.window.attributes("-fullscreen", False)
            
    def clear_canvas(self):
    
        self.c.delete(self.rect)
        self.c.delete(self.d1)
        self.c.delete(self.d2)
    
        self.window.update()

    
    def on_press(self, event):
        #print("Pressed", event)
    
        self.sx = event.x
        self.sy = event.y
    
        self.rect = self.c.create_rectangle(self.sx, self.sy, self.sx, self.sy, width=2, fill="", dash=(3,5), outline="green3")
    
        self.d1 = self.c.create_line(self.sx, self.sy, self.sx, self.sy, width=1.2, fill="green2")
        self.d2 = self.c.create_line(self.sx, self.sy, self.sx, self.sy, width=1.2, fill="green2")
    
    
    def on_drag(self, event):
        #print("Draged", event)
        self.cx = event.x
        self.cy = event.y

        self.c.coords(self.rect, self.sx, self.sy, self.cx, self.cy)
        self.c.coords(self.d1, self.sx, self.sy, self.cx, self.cy)
        self.c.coords(self.d2, self.cx, self.sy, self.sx, self.cy)
    
    
    def on_release(self, event):
        #print("Released", event)
        self.ex = event.x
        self.ey = event.y
    
        self.clear_canvas()
        self.snap = area_screenshot(self.sx, self.sy, self.ex, self.ey, self.name)
        self.close_snip_window()
    
    
    def close_snip_window(self):
        os.remove("temp.png")
        self.window.withdraw()
        self.window.quit()
    
    
    

#Main function
def take_snap():
    
    obj = _Snap()

    cx = (obj.sx + obj.ex) / 2
    cy = (obj.sy + obj.ey) / 2

    data = {
        "sxy": (obj.sx, obj.sy,),
        "exy": (obj.ex, obj.ey,),
        "cxy": (cx, cy,),
        "snap": obj.snap
        }

    del obj

    return data



#take_snap()

I tried with Toplevel() function but it didn't worked.

3

There are 3 best solutions below

1
On

When you create an image, it stored with the main Tk window by default. Since you are creating another Tk window, you need to specify the image window.

Try this update:

bg = PhotoImage(file=fname, master=self.window)  # link to child window
0
On

Following the other answers, after solving the issue if you are faced with the mss.exception.ScreenShotError: XDefaultRootWindow() failed error:

I solved this problem by instantiating the mss object only once as a global variable and using it over and over when grabbing screenshots instead of reinstantiating it.

like:

SCT = mss.mss()


def take_screenshot():
    global SCT  # don't know if this is needed tbh

    SCT.grab(bbox_value)

instead of this:

def take_screenshot():
    with mss.mss() as sct:
       sct.grab(bbox_value)

another solution could be to take the first screenshot in the main function: Threading and mss problem - error when running function for the second time

0
On

I just released the version 8.0.0 of MSS that fixes the Tk issue. Sorry, it take me so long to identify and fix :)