-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
ImageGrab fails with multiple monitors #1547
Description
When calling ImageGrab.grab() passing in a bounding box that is outside the area of my primary monitor, I just get black.
For example, my primary monitor is 1920x1200, flanked on either side by monitors running at 1600x1200, making my total desktop size 5120x1200. Also, because my primary monitor is in the middle, the horizontal coordinates for the full virtual desktop go from -1600 to 3519, where 0 is the left-most pixel of my primary monitor. If I try to capture my rightmost monitor using the following code, all I get is a black image:
from PIL import ImageGrab
img = ImageGrab.grab([1920, 0, 3519, 1199])
img.save("test.jpg")Poking around the code, it looks like ImageGrab.grab() calls into Image.core.grabscreen which is an alias for PyImaging_GrabScreenWin32() in display.c. That function does retrieve a DC handle to the entire desktop, but the subsequent calls to GetDeviceCaps with HORZRES and VERTRES only return the x/y size of the primary monitor, not the entire desktop.
screen = CreateDC("DISPLAY", NULL, NULL, NULL);
// ...
width = GetDeviceCaps(screen, HORZRES);
height = GetDeviceCaps(screen, VERTRES);
// ...
if (!BitBlt(screen_copy, 0, 0, width, height, screen, 0, 0, SRCCOPY))
goto error;Another problem with the above code is that monitors to the left of or above the primary display have negative coordinates in the screen DC. So, for example, capturing the monitor to the left of my primary display (which has a resolution of 1600x1200) would need to call BitBlt with the following coordinates:
left = -1600
top = 0
width = 1600
height = 1200
BitBlt(screen_copy, 0, 0, width, height, screen, left, top, SRCCOPY)Similarly, if I was trying to capture a monitor above my primary display, then top would be negative. Because of the negative coordinates issue, I don't see any way of fixing this without passing in left, top, width, height from the calling python code, which could be calculated easily from the bbox parameter. Then it's simply up to the caller to know the coordinates of the monitor they want to capture. If no bbox is provided, then the coordinates would default to the primary display (0, 0, HORZRES, VERTRES), keeping the current functionality unchanged so as not to break existing code that uses ImageGrab.grab().