Skip to content

ImageGrab.grab(bbox=) fails on Mac with Retina screen, or on secondary monitor #6144

@resnbl

Description

@resnbl

What did you do?

Displayed a window using tkinter and attempted to file a screenshot of it.

What did you expect to happen?

Create an image file showing the window contents.

What actually happened?

The generated image shows an area above and to the left of the intended target, and only covers an area 1/4th of the expected size.

What are your OS, Python and Pillow versions?

  • OS: Mac OS 12.2.1
  • Python: 3.10.1
  • Pillow: 9.0.1
import sys
import tkinter as tk
from tkinter import ttk
from PIL import ImageGrab, __version__ as pil_version


def callback():
    img = ImageGrab.grab()
    img.save('screen.png')

    geom = root.winfo_geometry()
    print('geom=', geom)
    left, top = root.winfo_rootx(), root.winfo_rooty()
    width, height = root.winfo_width(), root.winfo_height()
    print(f'{left=} {top=} {width=} {height=}')
    bbox = (left, top, left+width, top+height)
    img = ImageGrab.grab(bbox=bbox)
    img.save('window.png')


print('Python:', sys.version)
print('PIL:', pil_version)
root = tk.Tk()
root.title('ImageGrab Test')
root.geometry('200x300+100+200')
message = tk.Label(root, text='Hello, World!')
message.pack()
save_btn = ttk.Button(root, text='Save Me', command=lambda: callback())
save_btn.pack(ipadx=5, ipady=5, expand=True)
root.mainloop()

Below is a (cropped) version of the 'screen.png' file generated by my script:
screen

Here is the captured image:
window

ImageGrab.grab() is failing to account for the 144 DPI (not 72 DPI "virtual pixels") image generated by screencapture for my Retina display, so the bbox passed to the crop function needs to be scaled by 2 (all 4 parameters).

Secondary problem: this all fails completely when the app window is on a secondary monitor.

Proposed solution: Apple knows best!
Instead of capturing the entire screen and then cropping it down, make use of the provided -R{x,y,w,h} option for thescreencapture command. This will:

  • capture the right portion of the screen (Apple knows to scale "virtual" to "physical" pixels)
  • only load the desired portion of the screen (a much smaller image to load into memory)
  • works perfectly on any monitor (my 2nd display has left/top of -1920,0; .crop() can't deal with that bbox...)

[PS: for those outside the "reality distortion field", Apple's current lineup contains only Retina displays...]

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions