Search code examples
pythonmacostkinterpython-imaging-libraryscreenshot

How to capture image (screenshot) of tkinter window on MacOS


I have created a GUI app in Python tkinter for analyzing data in my laboratory. There are a number of buttons, figures, and canvas widgets. It would be helpful to take a screenshot of the entire window ('root') using just a single button that saves the filename appropriately. Example using Mac's built-in "screenshot" app here.

Related questions here, here, and here, but none worked successfully. The final link was almost successful, however the image that is saved is my computer's desktop background. My computer is a Mac, MacOS Monterey 12.0.1.

'root' is the tkinter window because

root = tk.Tk()

appears at the beginning of the script, analogous to 'window' in the example here. I'm using PIL.ImageGrab in the code sample below.

This is the current code, which takes an unhelpful screenshot of my desktop background,

def screenshot():
    # retrieve the time string to use as a filename
    file_name = root.time_string[-6:]
    full_file_name = file_name + '_summary' + '.png'
    x = root.winfo_rootx() + root.winfo_x()
    y = root.winfo_rooty() + root.winfo_y()
    x1 = x + root.winfo_width()
    y1 = y + root.winfo_height()
    ImageGrab.grab().crop((x, y, x1, y1)).save(full_file_name)

I create the button like so:

screenshot_btn = tk.Button(root, text='Screenshot', command=lambda: screenshot(), font=('Verdana', 24), state=DISABLED)

And I place the button in 'root' like this:

screenshot_btn.grid(row=11, column=3)

[This is my first post at stackoverflow. I apologize in advance if I did not follow all the guidelines perfectly on the first try. Thanks for your patience.]


Solution

  • First, I didn't have an issue with the grab showing my desktop, but it was showing an improperly cropped image.

    I have found a hacky solution. The issues appears to be with the resolution. So the dimensions need some scaling.

    What I did was get the output from ImageGrab.grab().save(full_file_name) ( no cropping ) and measure the size of the desired image area in pixels. These dimensions will be called x_pixels and y_pixels.

    Then I measured that same area on the actual window in screen units. I did this by bringing up the mac screenshot tool which shows the dimensions of an area. I then call these dimensions x_screen and y_screen. Then I modified your screenshot function as follows.

    def screenshot():
        # retrieve the time string to use as a filename
        file_name = root.time_string[-6:]
        full_file_name = file_name + '_summary' + '.png'
        x = root.winfo_rootx()
        y = root.winfo_rooty()
        x1 = x + root.winfo_width()
        y1 = y + root.winfo_height()
        x_pixels = 337
        y_pixels = 79
        x_screen = 171
        y_screen = 41
        x = x*(x_pixels/x_screen)
        y = y*(y_pixels/y_screen)
        x1 = x1*(x_pixels/x_screen)
        y1 = y1*(y_pixels/y_screen)
        ImageGrab.grab().crop((x, y, x1, y1)).save(full_file_name)
    

    Notice I also removed +root.winfo_x() and +root.winfo_y()

    The result is shown below. It's not perfect, but I believe if I more carefully measured the pixels at the bounds of the screenshot and window the scaling would be improved.

    Example Screenshot