def grab(self, monitor): # type: (Dict[str, int]) -> ScreenShot """ Retrieve all pixels from a monitor. Pixels have to be RGB. """ # Convert PIL bbox style if isinstance(monitor, tuple): monitor = { 'left': monitor[0], 'top': monitor[1], 'width': monitor[2] - monitor[0], 'height': monitor[3] - monitor[1], } ximage = self.xlib.XGetImage(self.display, self.drawable, monitor['left'], monitor['top'], monitor['width'], monitor['height'], PLAINMASK, ZPIXMAP) if not ximage: raise ScreenShotError('xlib.XGetImage() failed.', locals()) bits_per_pixel = ximage.contents.bits_per_pixel if bits_per_pixel != 32: raise ScreenShotError(('[XImage] bits per pixel value ' 'not (yet?) implemented.'), locals()) data = ctypes.cast( ximage.contents.data, ctypes.POINTER(ctypes.c_ubyte * monitor['height'] * monitor['width'] * 4)) data = bytearray(data.contents) # Free self.xlib.XDestroyImage(ximage) return self.cls_image(data, monitor)
def grab(self, monitor): # type: (Dict[str, int]) -> ScreenShot """ See :meth:`MSSBase.grab <mss.base.MSSBase.grab>` for full details. """ # Convert PIL bbox style if isinstance(monitor, tuple): monitor = { 'left': monitor[0], 'top': monitor[1], 'width': monitor[2] - monitor[0], 'height': monitor[3] - monitor[1], } core = self.core rect = CGRect((monitor['left'], monitor['top']), (monitor['width'], monitor['height'])) image_ref = core.CGWindowListCreateImage(rect, 1, 0, 0) if not image_ref: raise ScreenShotError( 'CoreGraphics.CGWindowListCreateImage() failed.', locals()) width = int(core.CGImageGetWidth(image_ref)) height = int(core.CGImageGetHeight(image_ref)) prov = copy_data = None try: prov = core.CGImageGetDataProvider(image_ref) copy_data = core.CGDataProviderCopyData(prov) data_ref = core.CFDataGetBytePtr(copy_data) buf_len = core.CFDataGetLength(copy_data) raw = ctypes.cast( data_ref, ctypes.POINTER(ctypes.c_ubyte * buf_len)) data = bytearray(raw.contents) # Remove padding per row bytes_per_row = int(core.CGImageGetBytesPerRow(image_ref)) bytes_per_pixel = int(core.CGImageGetBitsPerPixel(image_ref)) bytes_per_pixel = (bytes_per_pixel + 7) // 8 if bytes_per_pixel * width != bytes_per_row: cropped = bytearray() for row in range(height): start = row * bytes_per_row end = start + width * bytes_per_pixel cropped.extend(data[start:end]) data = cropped finally: if prov: core.CGDataProviderRelease(prov) if copy_data: core.CFRelease(copy_data) return self.cls_image(data, monitor, size=Size(width, height))
def __init__(self): # type: () -> None """ macOS initialisations. """ coregraphics = ctypes.util.find_library('CoreGraphics') if not coregraphics: raise ScreenShotError('No CoreGraphics library found.', locals()) self.core = ctypes.cdll.LoadLibrary(coregraphics) self._set_argtypes() self._set_restypes()
def __init__(self, display=None): # type: (bytes) -> None """ GNU/Linux initialisations. """ if not display: try: display = os.environ['DISPLAY'].encode('utf-8') except KeyError: raise ScreenShotError('$DISPLAY not set.', locals()) if not isinstance(display, bytes): display = display.encode('utf-8') if b':' not in display: raise ScreenShotError('Bad display value.', locals()) x11 = ctypes.util.find_library('X11') if not x11: raise ScreenShotError('No X11 library found.', locals()) self.xlib = ctypes.cdll.LoadLibrary(x11) xrandr = ctypes.util.find_library('Xrandr') if not xrandr: raise ScreenShotError('No Xrandr extension found.', locals()) self.xrandr = ctypes.cdll.LoadLibrary(xrandr) self._set_argtypes() self._set_restypes() self.display = self.xlib.XOpenDisplay(display) try: self.display.contents except ValueError: raise ScreenShotError('Cannot open display.', locals()) self.root = self.xlib.XDefaultRootWindow( self.display, self.xlib.XDefaultScreen(self.display)) # Fix for XRRGetScreenResources and XGetImage: # expected LP_Display instance instead of LP_XWindowAttributes self.drawable = ctypes.cast(self.root, ctypes.POINTER(Display))
def validate(value, _, args): # type: (int, Any, Tuple[Any, Any]) -> None """ Validate the returned value of xrandr.XRRGetScreenResources(). We can end on a segfault if not: Xlib: extension "RANDR" missing on display "...". """ if value == 0: raise ScreenShotError(('xrandr.XRRGetScreenResources() failed.' ' NULL pointer received.'), locals()) return args
def pixel(self, coord_x, coord_y): # type: (int, int) -> Tuple[int, int, int] """ Returns the pixel value at a given position. :param int coord_x: The x coordinate. :param int coord_y: The y coordinate. :return tuple: The pixel value as (R, G, B). """ try: return self.pixels[coord_y][coord_x] except IndexError: raise ScreenShotError('Pixel location out of range.', locals())
def mss(**kwargs): # type: (**str) -> MSS """ Factory returning a proper MSS class instance. It detects the plateform we are running on and choose the most adapted mss_class to take screenshots. It then proxies its arguments to the class for instantiation. """ operating_system = platform.system().lower() if operating_system == 'darwin': from .darwin import MSS elif operating_system == 'linux': from .linux import MSS elif operating_system == 'windows': from .windows import MSS else: raise ScreenShotError('System not (yet?) implemented.', locals()) return MSS(**kwargs)
def save(self, mon=0, output='monitor-{mon}.png', callback=None): # type: (int, str, Callable[[str], None]) -> Iterator[str] """ Grab a screen shot and save it to a file. :param int mon: The monitor to screen shot (default=0). -1: grab one screen shot of all monitors 0: grab one screen shot by monitor N: grab the screen shot of the monitor N :param str output: The output filename. It can take several keywords to customize the filename: - `{mon}`: the monitor number - `{top}`: the screen shot y-coordinate of the upper-left corner - `{left}`: the screen shot x-coordinate of the upper-left corner - `{width}`: the screen shot's width - `{height}`: the screen shot's height - `{date}`: the current date using the default formatter As it is using the `format()` function, you can specify formatting options like `{date:%Y-%m-%s}`. :param callable callback: Callback called before saving the screen shot to a file. Take the `output` argument as parameter. :return generator: Created file(s). """ monitors = self.monitors if not monitors: raise ScreenShotError('No monitor found.') if mon == 0: # One screen shot by monitor for idx, monitor in enumerate(monitors[1:], 1): fname = output.format(mon=idx, date=datetime.now(), **monitor) if callable(callback): callback(fname) sct = self.grab(monitor) to_png(sct.rgb, sct.size, level=self.compression_level, output=fname) yield fname else: # A screen shot of all monitors together or # a screen shot of the monitor N. mon = 0 if mon == -1 else mon try: monitor = monitors[mon] except IndexError: raise ScreenShotError('Monitor does not exist.', locals()) output = output.format(mon=mon, date=datetime.now(), **monitor) if callable(callback): callback(output) sct = self.grab(monitor) to_png(sct.rgb, sct.size, level=self.compression_level, output=output) yield output
def grab(self, monitor): # type: (Dict[str, int]) -> ScreenShot """ Retrieve all pixels from a monitor. Pixels have to be RGB. In the code, there are few interesting things: [1] bmi.bmiHeader.biHeight = -height A bottom-up DIB is specified by setting the height to a positive number, while a top-down DIB is specified by setting the height to a negative number. https://msdn.microsoft.com/en-us/library/ms787796.aspx https://msdn.microsoft.com/en-us/library/dd144879%28v=vs.85%29.aspx [2] bmi.bmiHeader.biBitCount = 32 image_data = create_string_buffer(height * width * 4) We grab the image in RGBX mode, so that each word is 32bit and we have no striding, then we transform to RGB. Inspired by https://github.com/zoofIO/flexx [3] bmi.bmiHeader.biClrUsed = 0 bmi.bmiHeader.biClrImportant = 0 When biClrUsed and biClrImportant are set to zero, there is "no" color table, so we can read the pixels of the bitmap retrieved by gdi32.GetDIBits() as a sequence of RGB values. Thanks to http://stackoverflow.com/a/3688682 """ # Convert PIL bbox style if isinstance(monitor, tuple): monitor = { 'left': monitor[0], 'top': monitor[1], 'width': monitor[2] - monitor[0], 'height': monitor[3] - monitor[1], } gdi = ctypes.windll.gdi32 width, height = monitor['width'], monitor['height'] if (self._bbox['height'], self._bbox['width']) != (height, width): self._bbox = monitor self._bmi.bmiHeader.biWidth = width self._bmi.bmiHeader.biHeight = -height # Why minus? [1] self._data = ctypes.create_string_buffer(width * height * 4) # [2] self._bmp = gdi.CreateCompatibleBitmap(self._srcdc, width, height) gdi.SelectObject(self._memdc, self._bmp) gdi.BitBlt(self._memdc, 0, 0, width, height, self._srcdc, monitor['left'], monitor['top'], SRCCOPY | CAPTUREBLT) bits = gdi.GetDIBits(self._memdc, self._bmp, 0, height, self._data, self._bmi, DIB_RGB_COLORS) if bits != height: del self._data raise ScreenShotError('gdi32.GetDIBits() failed.', locals()) return self.cls_image(self._data, monitor)