1

We can capture a video stream from a webcam with:

camera = cv2.VideoCapture(0)
while True:
    success, frame = camera.read()
    if success:
        buffer = cv2.imencode('.png', frame)

Is there a similar way in Python to capture a video stream of another window (at a 30 fps rate), even if this window is not in foreground? (Another window is on top)

I thought about methods mentioned in Can python get the screen shot of a specific window? but I doubt it will work because the window to be captured is not in foreground. Techincally it is hidden by another window on top of it.

Is there a more direct way with GDI WinApi?

Basj
  • 41,386
  • 99
  • 383
  • 673
  • [Screen capture](https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/screen-capture). – IInspectable May 17 '22 at 21:46
  • Good idea @IInspectable, I'll have a look. Out of curiosity, what were the possibilities before this relatively-new API? I imagine this was possible since older versions of Windows, which were the names of these APIs? – Basj May 17 '22 at 21:59
  • *"what were the possibilities before this relatively-new API?"* - Hooking into the compositor. – IInspectable May 17 '22 at 22:01
  • Would you have an example about how to do this @IInspectable? – Basj May 17 '22 at 22:04
  • Does [Desktop Duplication API](https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api) or [Capturing an Image](https://learn.microsoft.com/en-us/windows/win32/gdi/capturing-an-image)(GDI) work for you? – YangXiaoPo-MSFT May 18 '22 at 01:22
  • @YangXiaoPo-MSFT Interesting solutions. Which solution is the best if it has to work on Win10 and also Win7 (I know it's EOL but some clients still use it and I have to deal with this). Is the GDI solution better in this case? If you want, you can post an answer with a code example, it would be interesting for future reference. Thanks! – Basj May 18 '22 at 06:52
  • @YangXiaoPo-MSFT PS: does the GDI solution work also if the window to be captured is in the background, and another window is on top of it? – Basj May 18 '22 at 06:54

1 Answers1

0

Although GDI can capture the window which in the background, some area cannot be captured for some windows Or you can just capture the client area. If possible, use Screen capture as @IInspectable suggested.
The following code is adapted from document sample Capturing an Image.

int CaptureAnImage(HWND hWnd)
{
    HDC hdcScreen;
    HDC hdcWindow;
    HDC hdcMemDC = NULL;
    HBITMAP hbmScreen = NULL;
    BITMAP bmpScreen;
    DWORD dwBytesWritten = 0;
    DWORD dwSizeofDIB = 0;
    HANDLE hFile = NULL;
    char* lpbitmap = NULL;
    HANDLE hDIB = NULL;
    DWORD dwBmpSize = 0;

    // Retrieve the handle to a display device context for the client 
    // area of the window. 
    //hdcScreen = GetDC(NULL);
    /**/
    HWND hWindow = FindWindow(NULL, L"*Untitled - Notepad");
    //HWND hWindow = GetDesktopWindow();
    hdcScreen = GetWindowDC(hWindow);

    RECT r{};
    GetWindowRect(hWindow, &r);

    hdcWindow = GetDC(hWnd);

    // Create a compatible DC, which is used in a BitBlt from the window DC.
    hdcMemDC = CreateCompatibleDC(hdcWindow);

    if (!hdcMemDC)
    {
        MessageBox(hWnd, TEXT("CreateCompatibleDC has failed"), TEXT("Failed"), MB_OK);
        goto done;
    }

    // Get the client area for size calculation.
    RECT rcClient;
    GetClientRect(hWnd, &rcClient);

    // This is the best stretch mode.
    SetStretchBltMode(hdcWindow, HALFTONE);

    // The source DC is the entire screen, and the destination DC is the current window (HWND).
    if (!StretchBlt(hdcWindow,
        0, 0,
        rcClient.right, rcClient.bottom,
        hdcScreen,
        0, 0,
        //GetSystemMetrics(SM_CXSCREEN),
        r.right - r.left,
        r.bottom - r.top,
        //GetSystemMetrics(SM_CYSCREEN),
        SRCCOPY))
    {
        MessageBox(hWnd, TEXT("StretchBlt has failed"), TEXT("Failed"), MB_OK);
        goto done;
    }

    // Create a compatible bitmap from the Window DC.
    hbmScreen = CreateCompatibleBitmap(hdcWindow, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);

    if (!hbmScreen)
    {
        MessageBox(hWnd, TEXT("CreateCompatibleBitmap Failed"), TEXT("Failed"), MB_OK);
        goto done;
    }

    // Select the compatible bitmap into the compatible memory DC.
    SelectObject(hdcMemDC, hbmScreen);

    // Bit block transfer into our compatible memory DC.
    if (!BitBlt(hdcMemDC,
        0, 0,
        rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
        hdcWindow,
        0, 0,
        SRCCOPY))
    {
        MessageBox(hWnd, TEXT("BitBlt has failed"), TEXT("Failed"), MB_OK);
        goto done;
    }

    // Get the BITMAP from the HBITMAP.
    GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);

    BITMAPFILEHEADER   bmfHeader;
    BITMAPINFOHEADER   bi;

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bmpScreen.bmWidth;
    bi.biHeight = bmpScreen.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = 32;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;

    // Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that 
    // call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc 
    // have greater overhead than HeapAlloc.
    hDIB = GlobalAlloc(GHND, dwBmpSize);
    lpbitmap = (char*)GlobalLock(hDIB);

    // Gets the "bits" from the bitmap, and copies them into a buffer 
    // that's pointed to by lpbitmap.
    GetDIBits(hdcWindow, hbmScreen, 0,
        (UINT)bmpScreen.bmHeight,
        lpbitmap,
        (BITMAPINFO*)&bi, DIB_RGB_COLORS);

    // A file is created, this is where we will save the screen capture.
    hFile = CreateFile(TEXT("captureqwsx.bmp"),
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL, NULL);

    // Add the size of the headers to the size of the bitmap to get the total file size.
    dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    // Offset to where the actual bitmap bits start.
    bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);

    // Size of the file.
    bmfHeader.bfSize = dwSizeofDIB;

    // bfType must always be BM for Bitmaps.
    bmfHeader.bfType = 0x4D42; // BM.

    WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);

    // Unlock and Free the DIB from the heap.
    GlobalUnlock(hDIB);
    GlobalFree(hDIB);

    // Close the handle for the file that was created.
    CloseHandle(hFile);

    // Clean up.
done:
    DeleteObject(hbmScreen);
    DeleteObject(hdcMemDC);
    ReleaseDC(NULL, hdcScreen);
    ReleaseDC(hWnd, hdcWindow);

    return 0;
}

enter image description here enter image description here

YangXiaoPo-MSFT
  • 1,589
  • 1
  • 4
  • 22