Question

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.

Was it helpful?

Solution

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)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top