Repository: python-pillow/Pillow

Issue:
ImageGrab.grab(bbox=) fails on Mac with Retina screen, or on secondary monitor
### 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

```python
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:
<img width="1176" alt="screen" src="https://user-images.githubusercontent.com/33208090/159143737-46714c3b-8b73-492f-afea-f2e75e94b685.png">

Here is the captured image:
![window](https://user-images.githubusercontent.com/33208090/159143744-3e487cca-624f-4614-a2a2-7c13771b5547.png)

`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 the`screencapture` 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...]


Discussion:

Resolving patch:
diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py
--- a/src/PIL/ImageGrab.py
+++ b/src/PIL/ImageGrab.py
@@ -30,14 +30,18 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
         if sys.platform == "darwin":
             fh, filepath = tempfile.mkstemp(".png")
             os.close(fh)
-            subprocess.call(["screencapture", "-x", filepath])
+            args = ["screencapture"]
+            if bbox:
+                left, top, right, bottom = bbox
+                args += ["-R", f"{left},{right},{right-left},{bottom-top}"]
+            subprocess.call(args + ["-x", filepath])
             im = Image.open(filepath)
             im.load()
             os.unlink(filepath)
             if bbox:
-                im_cropped = im.crop(bbox)
+                im_resized = im.resize((right - left, bottom - top))
                 im.close()
-                return im_cropped
+                return im_resized
             return im
         elif sys.platform == "win32":
             offset, size, data = Image.core.grabscreen_win32(


Test patch: