Question solved!
PIL.ImageGrab.grab(bbox=None, include_layered_windows=False, all_screens=True)
And it captures every screen.
Answer from Qiasm on Stack OverflowImageGrab range error when capturing multi-screen shots and the parameter 'left' is negative
Hacktober opportunity: Add multimonitor support to pyautogui for windows
PyAutoGUI with multiple monitors
python - PIL ImageGrab fails on 2nd virtual monitor of VirtualBox - Stack Overflow
I came across a problem today that could be solved by a beginner / intermediate python programmer and a little time. Anyone want to take on a challenge?
The pyautogui.screenshot() function on Windows does not support multiple monitors. This has been a problem for many years, but recently the PIL ImageGrab module (which pyautogui depends on) added the all_screens option.
# primary monitor only import pyautogui im = pyautogui.screenshot() # all monitors from PIL import ImageGrab im = ImageGrab.grab(all_screens=True)
This new feature just needs to be added to pyautogui / pyscreeze. Easy(ish). You would learn a lot, get listed as a contributor to a popular open source project (great for resumes), and it would count toward a hacktoberfest tshirt!
Here's the issue you would close: https://github.com/asweigart/pyautogui/issues/9
Here's the offending line at the core of what needs to be updated: https://github.com/asweigart/pyscreeze/blob/master/pyscreeze/init.py#L427
Just wondering if anyone else has run into problems using pyautogui.locateCenterOnScreen with multiple monitors and if so, did you find any solution to help the function. I keep getting a None value returned when the image is clearly on the screen.
Thanks
I've faced this same issue. If I had to guess, I'd assume that ImageGrab is getting its coordinates from the SM_SCREEN flag, which gives the coordinates of the primary monitor only, rather than the SM_VIRTUALSCREEN, which gives the entire virtual screen (I haven't looked through the source though, so just a guess).
That said, it's easy enough to get around this by interacting directly with the Windows API, then converting its bitmap output into a more usable PIL object.
Some Code:
def _get_screen_buffer(self, bounds=None):
# Grabs a DC to the entire virtual screen, but only copies to
# the bitmap the the rect defined by the user.
SM_XVIRTUALSCREEN = 76 # coordinates for the left side of the virtual screen.
SM_YVIRTUALSCREEN = 77 # coordinates for the right side of the virtual screen.
SM_CXVIRTUALSCREEN = 78 # width of the virtual screen
SM_CYVIRTUALSCREEN = 79 # height of the virtual screen
hDesktopWnd = windll.user32.GetDesktopWindow() #Entire virtual Screen
left = windll.user32.GetSystemMetrics(SM_XVIRTUALSCREEN)
top = windll.user32.GetSystemMetrics(SM_YVIRTUALSCREEN)
width = windll.user32.GetSystemMetrics(SM_CXVIRTUALSCREEN)
height = windll.user32.GetSystemMetrics(SM_CYVIRTUALSCREEN)
if bounds:
left, top, right, bottom = bounds
width = right - left
height = bottom - top
hDesktopDC = windll.user32.GetWindowDC(hDesktopWnd)
if not hDesktopDC: print 'GetDC Failed'; sys.exit()
hCaptureDC = windll.gdi32.CreateCompatibleDC(hDesktopDC)
if not hCaptureDC: print 'CreateCompatibleBitmap Failed'; sys.exit()
hCaptureBitmap = windll.gdi32.CreateCompatibleBitmap(hDesktopDC, width, height)
if not hCaptureBitmap: print 'CreateCompatibleBitmap Failed'; sys.exit()
windll.gdi32.SelectObject(hCaptureDC, hCaptureBitmap)
SRCCOPY = 0x00CC0020
windll.gdi32.BitBlt(
hCaptureDC,
0, 0,
width, height,
hDesktopDC,
left, top,
0x00CC0020
)
return hCaptureBitmap
def _make_image_from_buffer(self, hCaptureBitmap):
import Image
bmp_info = BITMAPINFO()
bmp_header = BITMAPFILEHEADER()
hdc = windll.user32.GetDC(None)
bmp_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER)
DIB_RGB_COLORS = 0
windll.gdi32.GetDIBits(hdc,
hCaptureBitmap,
0,0,
None, byref(bmp_info),
DIB_RGB_COLORS
)
bmp_info.bmiHeader.biSizeImage = bmp_info.bmiHeader.biWidth *abs(bmp_info.bmiHeader.biHeight) * (bmp_info.bmiHeader.biBitCount+7)/8;
size = (bmp_info.bmiHeader.biWidth, bmp_info.bmiHeader.biHeight )
print size
pBuf = (c_char * bmp_info.bmiHeader.biSizeImage)()
windll.gdi32.GetBitmapBits(hCaptureBitmap, bmp_info.bmiHeader.biSizeImage, pBuf)
return Image.frombuffer('RGB', size, pBuf, 'raw', 'BGRX', 0, 1)
The first function gets a bitmap of the screen, and the second guy converts it to a PIL object.
If you don't want to figure out the coordinates of the other monitors yourself, I've got a small module called PyRobot which has functions for targeting specific monitors and whatnot. And it's pure Python, so no need to install PyWin32 :)
Audionautics / chriskiehl !
I took your post and some of your library and re-write it for python 3 ! I hope you're okay with it. It works for me using PILLOW (PIL) and the python 3.4 engine.
I just called the file duelMonitory.py containing:
# https://github.com/chriskiehl/pyrobot
# started from Audionautics code on http://stackoverflow.com/questions/3585293/pil-imagegrab-fails-on-2nd-virtual-monitor-of-virtualbox
# updated for PILLOW and Python 3 by Alan Baines (Kizrak)
from PIL import *
#from ctypes import windll, Structure, byref, c_uint
#import ctypes
import ctypes
from ctypes import *
from ctypes.wintypes import *
def get_screen_buffer(bounds=None):
# Grabs a DC to the entire virtual screen, but only copies to
# the bitmap the the rect defined by the user.
SM_XVIRTUALSCREEN = 76 # coordinates for the left side of the virtual screen.
SM_YVIRTUALSCREEN = 77 # coordinates for the right side of the virtual screen.
SM_CXVIRTUALSCREEN = 78 # width of the virtual screen
SM_CYVIRTUALSCREEN = 79 # height of the virtual screen
hDesktopWnd = windll.user32.GetDesktopWindow() #Entire virtual Screen
left = windll.user32.GetSystemMetrics(SM_XVIRTUALSCREEN)
top = windll.user32.GetSystemMetrics(SM_YVIRTUALSCREEN)
width = windll.user32.GetSystemMetrics(SM_CXVIRTUALSCREEN)
height = windll.user32.GetSystemMetrics(SM_CYVIRTUALSCREEN)
if bounds:
left, top, right, bottom = bounds
width = right - left
height = bottom - top
hDesktopDC = windll.user32.GetWindowDC(hDesktopWnd)
if not hDesktopDC:
print ('GetDC Failed')
sys.exit()
hCaptureDC = windll.gdi32.CreateCompatibleDC(hDesktopDC)
if not hCaptureDC:
print ('CreateCompatibleBitmap Failed')
sys.exit()
hCaptureBitmap = windll.gdi32.CreateCompatibleBitmap(hDesktopDC, width, height)
if not hCaptureBitmap:
print ('CreateCompatibleBitmap Failed')
sys.exit()
windll.gdi32.SelectObject(hCaptureDC, hCaptureBitmap)
SRCCOPY = 0x00CC0020
windll.gdi32.BitBlt(
hCaptureDC,
0, 0,
width, height,
hDesktopDC,
left, top,
0x00CC0020
)
return hCaptureBitmap
def make_image_from_buffer(hCaptureBitmap):
from PIL import Image
bmp_info = BITMAPINFO()
bmp_header = BITMAPFILEHEADER()
hdc = windll.user32.GetDC(None)
bmp_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER)
DIB_RGB_COLORS = 0
windll.gdi32.GetDIBits(hdc,
hCaptureBitmap,
0,0,
None, byref(bmp_info),
DIB_RGB_COLORS
)
bmp_info.bmiHeader.biSizeImage = int( bmp_info.bmiHeader.biWidth *abs(bmp_info.bmiHeader.biHeight) * (bmp_info.bmiHeader.biBitCount+7)/8 );
size = (bmp_info.bmiHeader.biWidth, bmp_info.bmiHeader.biHeight )
print (size)
pBuf = (c_char * bmp_info.bmiHeader.biSizeImage)()
windll.gdi32.GetBitmapBits(hCaptureBitmap, bmp_info.bmiHeader.biSizeImage, pBuf)
return Image.frombuffer('RGB', size, pBuf, 'raw', 'BGRX', 0, 1)
class BITMAPFILEHEADER(ctypes.Structure):
_fields_ = [
('bfType', ctypes.c_short),
('bfSize', ctypes.c_uint32),
('bfReserved1', ctypes.c_short),
('bfReserved2', ctypes.c_short),
('bfOffBits', ctypes.c_uint32)
]
class BITMAPINFOHEADER(ctypes.Structure):
_fields_ = [
('biSize', ctypes.c_uint32),
('biWidth', ctypes.c_int),
('biHeight', ctypes.c_int),
('biPlanes', ctypes.c_short),
('biBitCount', ctypes.c_short),
('biCompression', ctypes.c_uint32),
('biSizeImage', ctypes.c_uint32),
('biXPelsPerMeter', ctypes.c_long),
('biYPelsPerMeter', ctypes.c_long),
('biClrUsed', ctypes.c_uint32),
('biClrImportant', ctypes.c_uint32)
]
class BITMAPINFO(ctypes.Structure):
_fields_ = [
('bmiHeader', BITMAPINFOHEADER),
('bmiColors', ctypes.c_ulong * 3)
]
And my test code was duelMonitor.test.py containing:
from PIL import *
from duelMonitor import *
hCaptureBitmap = get_screen_buffer()
pimage = make_image_from_buffer(hCaptureBitmap)
pimage.save("Hello.png","PNG")
Basically just combination of Audionautics post and his library from https://github.com/chriskiehl/pyrobot webpage and conversion to Python 3 (and PILLOW).
Have fun!
(PS. I would have posted as comment, but I don't have enough points /sadface)