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_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