def get_image(self, x=0, y=0, width=0, height=0): start = time.time() metrics = get_virtualscreenmetrics() if self.metrics is None or self.metrics != metrics: #new metrics, start from scratch: self.metrics = metrics self.clean() log("get_image%s metrics=%s", (x, y, width, height), metrics) dx, dy, dw, dh = metrics if width == 0: width = dw if height == 0: height = dh #clamp rectangle requested to the virtual desktop size: if x < dx: width -= x - dx x = dx if y < dy: height -= y - dy y = dy if width > dw: width = dw if height > dh: height = dh if not self.dc: self.wnd = GetDesktopWindow() self.dc = GetWindowDC(self.wnd) assert self.dc, "failed to get a drawing context from the desktop window %s" % self.wnd self.bit_depth = GetDeviceCaps(self.dc, win32con.BITSPIXEL) self.memdc = CreateCompatibleDC(self.dc) assert self.memdc, "failed to get a compatible drawing context from %s" % self.dc bitmap = CreateCompatibleBitmap(self.dc, width, height) assert bitmap, "failed to get a compatible bitmap from %s" % self.dc r = SelectObject(self.memdc, bitmap) if r == 0: log.error("Error: cannot select bitmap object") return None select_time = time.time() log("get_image up to SelectObject (%s) took %ims", REGION_CONSTS.get(r, r), (select_time - start) * 1000) try: if BitBlt(self.memdc, 0, 0, width, height, self.dc, x, y, win32con.SRCCOPY) == 0: e = ctypes.get_last_error() #rate limit the error message: now = time.time() if now - self.bitblt_err_time > 10: log.error("Error: failed to blit the screen, error %i", e) self.bitblt_err_time = now return None except Exception as e: log("BitBlt error", exc_info=True) log.error("Error: cannot capture screen") log.error(" %s", e) return None bitblt_time = time.time() log("get_image BitBlt took %ims", (bitblt_time - select_time) * 1000) rowstride = roundup(width * self.bit_depth // 8, 2) buf_size = rowstride * height pixels = ctypes.create_string_buffer(b"", buf_size) log("GetBitmapBits(%#x, %#x, %#x)", bitmap, buf_size, ctypes.addressof(pixels)) r = GetBitmapBits(bitmap, buf_size, ctypes.byref(pixels)) if r == 0: log.error("Error: failed to copy screen bitmap data") return None if r != buf_size: log.warn( "Warning: truncating pixel buffer, got %i bytes but expected %i", r, buf_size) pixels = pixels[:r] log("get_image GetBitmapBits took %ims", (time.time() - bitblt_time) * 1000) DeleteObject(bitmap) assert pixels, "no pixels returned from GetBitmapBits" if self.bit_depth == 32: rgb_format = "BGRX" elif self.bit_depth == 30: rgb_format = "r210" elif self.bit_depth == 24: rgb_format = "BGR" elif self.bit_depth == 16: rgb_format = "BGR565" elif self.bit_depth == 8: rgb_format = "RLE8" else: raise Exception("unsupported bit depth: %s" % self.bit_depth) bpp = self.bit_depth // 8 v = ImageWrapper(0, 0, width, height, pixels, rgb_format, self.bit_depth, rowstride, bpp, planes=ImageWrapper.PACKED, thread_safe=True) if self.bit_depth == 8: count = GetSystemPaletteEntries(self.dc, 0, 0, None) log("palette size: %s", count) palette = [] if count > 0: buf = (PALETTEENTRY * count)() r = GetSystemPaletteEntries(self.dc, 0, count, ctypes.byref(buf)) #we expect 16-bit values, so bit-shift them: for p in buf: palette.append( (p.peRed << 8, p.peGreen << 8, p.peBlue << 8)) v.set_palette(palette) log("get_image%s=%s took %ims", (x, y, width, height), v, (time.time() - start) * 1000) return v
def get_cursor_data(hCursor): #w, h = get_fixed_cursor_size() if not hCursor: return None x, y = 0, 0 dc = None memdc = None bitmap = None old_handle = None pixels = None try: ii = ICONINFO() ii.cbSize = sizeof(ICONINFO) if not GetIconInfo(hCursor, byref(ii)): raise WindowsError() #@UndefinedVariable x = ii.xHotspot y = ii.yHotspot cursorlog( "get_cursor_data(%#x) hotspot at %ix%i, hbmColor=%#x, hbmMask=%#x", hCursor, x, y, ii.hbmColor or 0, ii.hbmMask or 0) if not ii.hbmColor: #FIXME: we don't handle black and white cursors return None iie = ICONINFOEXW() iie.cbSize = sizeof(ICONINFOEXW) if not GetIconInfoExW(hCursor, byref(iie)): raise WindowsError() #@UndefinedVariable name = iie.szResName[:MAX_PATH] cursorlog("wResID=%#x, sxModName=%s, szResName=%s", iie.wResID, iie.sxModName[:MAX_PATH], name) bm = Bitmap() if not GetObjectA(ii.hbmColor, sizeof(Bitmap), byref(bm)): raise WindowsError() #@UndefinedVariable cursorlog( "cursor bitmap: type=%i, width=%i, height=%i, width bytes=%i, planes=%i, bits pixel=%i, bits=%#x", bm.bmType, bm.bmWidth, bm.bmHeight, bm.bmWidthBytes, bm.bmPlanes, bm.bmBitsPixel, bm.bmBits or 0) w = bm.bmWidth h = bm.bmHeight dc = GetDC(None) assert dc, "failed to get a drawing context" memdc = CreateCompatibleDC(dc) assert memdc, "failed to get a compatible drawing context from %s" % dc bitmap = CreateCompatibleBitmap(dc, w, h) assert bitmap, "failed to get a compatible bitmap from %s" % dc old_handle = SelectObject(memdc, bitmap) #check if icon is animated: UINT_MAX = 2**32 - 1 if not DrawIconEx(memdc, 0, 0, hCursor, w, h, UINT_MAX, 0, 0): cursorlog("cursor is animated!") #if not DrawIcon(memdc, 0, 0, hCursor): if not DrawIconEx(memdc, 0, 0, hCursor, w, h, 0, 0, win32con.DI_NORMAL): raise WindowsError() #@UndefinedVariable buf_size = bm.bmWidthBytes * h buf = create_string_buffer(b"", buf_size) r = GetBitmapBits(bitmap, buf_size, byref(buf)) cursorlog("get_cursor_data(%#x) GetBitmapBits(%#x, %#x, %#x)=%i", hCursor, bitmap, buf_size, addressof(buf), r) if not r: cursorlog.error("Error: failed to copy screen bitmap data") return None elif r != buf_size: cursorlog.warn( "Warning: invalid cursor buffer size, got %i bytes but expected %i", r, buf_size) return None else: #32-bit data: pixels = bytearray(strtobytes(buf.raw)) has_alpha = False has_pixels = False for i in range(len(pixels) // 4): has_pixels = has_pixels or pixels[i * 4] != 0 or pixels[ i * 4 + 1] != 0 or pixels[i * 4 + 2] != 0 has_alpha = has_alpha or pixels[i * 4 + 3] != 0 if has_pixels and has_alpha: break if has_pixels and not has_alpha: #generate missing alpha - don't ask me why for i in range(len(pixels) // 4): if pixels[i * 4] != 0 or pixels[i * 4 + 1] != 0 or pixels[i * 4 + 2] != 0: pixels[i * 4 + 3] = 0xff return [0, 0, w, h, x, y, hCursor, bytes(pixels), strtobytes(name)] except Exception as e: cursorlog("get_cursor_data(%#x)", hCursor, exc_info=True) cursorlog.error("Error: failed to grab cursor:") cursorlog.error(" %s", str(e) or type(e)) return None finally: if old_handle: SelectObject(memdc, old_handle) if bitmap: DeleteObject(bitmap) if memdc: DeleteDC(memdc) if dc: ReleaseDC(None, dc)
def get_image(self, x=0, y=0, width=0, height=0): start = time.time() x, y, width, height = self.get_capture_coords(x, y, width, height) if not self.dc: self.wnd = GetDesktopWindow() if not self.wnd: log.error("Error: cannot access the desktop window") log.error(" capturing the screen is not possible") return None self.dc = GetWindowDC(self.wnd) if not self.dc: log.error("Error: cannot get a drawing context") log.error(" capturing the screen is not possible") log.error(" desktop window=%#x", self.wnd) return None self.bit_depth = GetDeviceCaps(self.dc, win32con.BITSPIXEL) self.memdc = CreateCompatibleDC(self.dc) assert self.memdc, "failed to get a compatible drawing context from %s" % self.dc bitmap = CreateCompatibleBitmap(self.dc, width, height) if not bitmap: log.error("Error: failed to get a compatible bitmap") log.error(" from drawing context %#x with size %ix%i", self.dc, width, height) self.clean_dc() return None r = SelectObject(self.memdc, bitmap) if not r: log.error("Error: cannot select bitmap object") return None select_time = time.time() log("get_image up to SelectObject (%s) took %ims", REGION_CONSTS.get(r, r), (select_time - start) * 1000) try: if BitBlt(self.memdc, 0, 0, width, height, self.dc, x, y, win32con.SRCCOPY) == 0: e = ctypes.get_last_error() #rate limit the error message: now = time.time() if now - self.bitblt_err_time > 10: log.error("Error: failed to blit the screen, error %i", e) self.bitblt_err_time = now return None except Exception as e: log("BitBlt error", exc_info=True) log.error("Error: cannot capture screen with BitBlt") log.error(" %s", e) self.clean_dc() return None bitblt_time = time.time() log("get_image BitBlt took %ims", (bitblt_time - select_time) * 1000) rowstride = roundup(width * self.bit_depth // 8, 2) buf_size = rowstride * height pixels = ctypes.create_string_buffer(b"", buf_size) log("GetBitmapBits(%#x, %#x, %#x)", bitmap, buf_size, ctypes.addressof(pixels)) r = GetBitmapBits(bitmap, buf_size, ctypes.byref(pixels)) if not r: log.error("Error: failed to copy screen bitmap data") self.clean_dc() return None if r != buf_size: log.warn( "Warning: truncating pixel buffer, got %i bytes but expected %i", r, buf_size) pixels = pixels[:r] log("get_image GetBitmapBits took %ims", (time.time() - bitblt_time) * 1000) DeleteObject(bitmap) assert pixels, "no pixels returned from GetBitmapBits" rgb_format = RGB_FORMATS.get(self.bit_depth) if not rgb_format: raise Exception("unsupported bit depth: %s" % self.bit_depth) bpp = self.bit_depth // 8 v = ImageWrapper(0, 0, width, height, pixels, rgb_format, self.bit_depth, rowstride, bpp, planes=ImageWrapper.PACKED, thread_safe=True) if self.bit_depth == 8: palette = get_palette(self.dc) v.set_palette(palette) log("get_image%s=%s took %ims", (x, y, width, height), v, (time.time() - start) * 1000) return v