仅针对某些应用程序无法使用 winAPI BitBlt 捕获窗口 [英] Trouble capturing window with winAPI BitBlt for some applications only

查看:91
本文介绍了仅针对某些应用程序无法使用 winAPI BitBlt 捕获窗口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经编写了一个简单的 Python 脚本,用于截取名称包含特定字符串的窗口的屏幕截图.我使用的代码如下,

<预><代码>导入 win32gui、win32ui、win32con导入 PIL.Imagedef getWindowHandle(name):窗口列表 = []win32gui.EnumWindows(lambda hwnd, wndList:wndList.append((win32gui.GetWindowText(hwnd), hwnd)),窗口列表)在 windowList 中配对:如果名称成对[0]:返回对[1]返回无类窗口():def __init__(self, hwnd = None):如果不是 hwnd:返回l, t, r, b = win32gui.GetClientRect(hwnd)sl, st, _, _ = win32gui.GetWindowRect(hwnd)cl, ct = win32gui.ClientToScreen(hwnd, (l, t))self.size = (r - l, b - t)self.position = (cl - sl, ct - st)hDC = win32gui.GetWindowDC(hwnd)self.windowDC = win32ui.CreateDCFromHandle(hDC)self.newDC = self.windowDC.CreateCompatibleDC()#win32gui.ReleaseDC(hwnd, hDC)self.bitmap = win32ui.CreateBitmap()self.bitmap.CreateCompatibleBitmap(self.windowDC, self.size[0], self.size[1])self.newDC.SelectObject(self.bitmap)def __del__(self):self.newDC.DeleteDC()self.windowDC.DeleteDC()删除 self.bitmapdef 截图(self, location = 'C:\\Users\\Grieverheart\\Desktop\\'):self.newDC.BitBlt((0, 0), self.size, self.windowDC, self.position, win32con.SRCCOPY)self.bitmap.Paint(self.newDC)bmpinfo = self.bitmap.GetInfo()bmpstr = self.bitmap.GetBitmapBits(True)im = PIL.Image.frombuffer('RGB', self.size, bmpstr, 'raw', 'BGRX', 0, 1)尝试:im.save(location + 'test.png', 'PNG')除了 IOError:返回定义主():handle = getWindowHandle("Blender")如果不处理:返回窗口 = 窗口(句柄)window.screenshot()如果 __name__ == "__main__":主要的()

脚本为我提供了一些应用程序的黑色屏幕截图,例如 Blender 或 DOSBox.

有谁知道是什么导致了某些应用程序出现此问题以及我该如何解决?

编辑:看来我的问题可能与this 帖子,虽然我不确定我必须做什么才能解决我的问题.我还想补充一点,我也尝试过添加 CAPTUREBLT 标志而没有任何区别.

解决方案

来自 MSDN

<块引用>

如果源和目标设备上下文代表不同的设备,BitBlt 将返回错误.要在不同设备的 DC 之间传输数据,请通过调用 GetDIBits 将内存位图转换为 DIB.要将 DIB 显示到第二个设备,请调用 SetDIBits 或 StretchDIBits.

这是什么意思?简而言之,混合 DWM(即 Aero)和非 GDI 应用程序(例如 OpenGL)使 BiBlt 变得不可预测/不可靠.

再次来自 MSDN:

<块引用>

Windows Vista 中引入的桌面合成功能从根本上改变了应用程序在屏幕上显示像素的方式.启用桌面合成后,单个窗口不再像在以前版本的 Windows 中那样直接绘制到屏幕或主显示设备上.相反,他们的绘图被重定向到视频内存中的屏幕外表面,然后呈现为桌面图像并呈现在显示器上.

由于使用 DWM 进行 blitting 有这些 了解问题,您可以:

  1. 尝试使用替代技术;你有 一个不错的在这里列出

  2. 您可以使用 DwmEnableComposition(DWM_EC_DISABLECOMPOSITION);

  3. 禁用 DWM(暂时)

试试看是否适合你.

AFAIK 但是,可靠地获取使用 3D 渲染(DirectX 或 OpenGL)的应用程序内容的唯一方法是将自己注入进程并复制这些位(请参阅 DirectX 的这个答案,或者钩住 wglSwapBuffers 并使用 glReadPixels 为 OpenGL 做一个回读)

I've put together a simple python script that is supposed to take a screenshot of a window whose name contains a specific string. The code I use is the following,


import win32gui, win32ui, win32con
import PIL.Image

def getWindowHandle(name):        
    windowList = []
    win32gui.EnumWindows(lambda hwnd, wndList:
                             wndList.append((win32gui.GetWindowText(hwnd), hwnd)),
                         windowList)

    for pair in windowList:
        if name in pair[0]:
            return pair[1]

    return None


class Window():
    def __init__(self, hwnd = None):
        if not hwnd: return

        l, t, r, b   = win32gui.GetClientRect(hwnd)
        sl, st, _, _ = win32gui.GetWindowRect(hwnd)
        cl, ct       = win32gui.ClientToScreen(hwnd, (l, t))

        self.size     = (r - l, b - t)
        self.position = (cl - sl, ct - st)

        hDC   = win32gui.GetWindowDC(hwnd)
        self.windowDC  = win32ui.CreateDCFromHandle(hDC)
        self.newDC = self.windowDC.CreateCompatibleDC()

        #win32gui.ReleaseDC(hwnd, hDC)

        self.bitmap = win32ui.CreateBitmap()
        self.bitmap.CreateCompatibleBitmap(self.windowDC, self.size[0], self.size[1])
        self.newDC.SelectObject(self.bitmap)

    def __del__(self):
        self.newDC.DeleteDC()
        self.windowDC.DeleteDC()
        del self.bitmap

    def screenshot(self, location = 'C:\\Users\\Grieverheart\\Desktop\\'):
        self.newDC.BitBlt((0, 0), self.size, self.windowDC, self.position, win32con.SRCCOPY)
        self.bitmap.Paint(self.newDC)

        bmpinfo = self.bitmap.GetInfo()
        bmpstr  = self.bitmap.GetBitmapBits(True)
        im = PIL.Image.frombuffer('RGB', self.size, bmpstr, 'raw', 'BGRX', 0, 1)
        try:
            im.save(location + 'test.png', 'PNG')
        except IOError:
            return


def main():
    handle = getWindowHandle("Blender")
    if not handle: return

    window = Window(handle)
    window.screenshot()

if __name__ == "__main__":
    main()

The script gives me a black screenshot for some applications, such as Blender or DOSBox.

Does anyone know what causes this issue for only some applications and how I could fix it?

EDIT: It seems my problem might be related to this post, although I am not sure what I'd have to do to fix my issue. I'd also like to add that I have also tried the adding CAPTUREBLT flag without any differences.

解决方案

From MSDN

BitBlt returns an error if the source and destination device contexts represent different devices. To transfer data between DCs for different devices, convert the memory bitmap to a DIB by calling GetDIBits. To display the DIB to the second device, call SetDIBits or StretchDIBits.

What does this mean? In short, mixing DWM (i.e. Aero) and non-GDI applications (OpenGL, for example) makes BiBlt a unpredictable/unreliable.

Again from MSDN:

The desktop composition feature, introduced in Windows Vista, fundamentally changed the way applications display pixels on the screen. When desktop composition is enabled, individual windows no longer draw directly to the screen or primary display device as they did in previous versions of Windows. Instead, their drawing is redirected to off-screen surfaces in video memory, which are then rendered into a desktop image and presented on the display.

Since blitting with the DWM has these know issues, you may:

  1. try to use alternative techniques; you have a nice list here

  2. you may disable DWM (temporarly) using DwmEnableComposition(DWM_EC_DISABLECOMPOSITION);

Try and see if any works for you.

AFAIK however, the only way to reliably get the content of an application which uses 3D rendering (DirectX or OpenGL) is to is to inject yourself into the process and copy out the bits (see this answer for DirectX, or hook wglSwapBuffers and do a readback with glReadPixels for OpenGL)

这篇关于仅针对某些应用程序无法使用 winAPI BitBlt 捕获窗口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆