1

I can successfully capture parts of the screen using BitBlt and GetDIBits thanks to the code in Nick Nougat's answer here.

Capturing the entire screen or desktop seems to work but when I provide the HDC of an app, it prints weird data (in bgra format).

HWND dekstopHWND = GetDesktopWindow();
// prints correct desktop pixels

HWND appHWND = FindWindowA(NULL, "Hello World!"); // working handle of an electron app
//prints 0 0 0 0 as pixels

HWND appHWND = FindWindowA(NULL, "Untitled - Notepad"); // Notepad app
//prints 255 255 255 0 as pixels

...

  • The function GetDeviceCaps says that the electron app supports BitBlt and the device TECHNOLOGY is raster display.
  • The width of the app's DC is always full screen, independent of the window size: printf("width %d\n", GetDeviceCaps(GetDC(appHWND), HORZRES)); //1920, is that correct behaviour?

I'm very new to Windows API... Which one of the steps or functions can be causing this? Thank you.

....

HBITMAP GetScreenBmp(HDC hdc) {
    int nScreenWidth = 100;
    int nScreenHeight = 100;
    HDC hCaptureDC = CreateCompatibleDC(hdc);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdc, nScreenWidth, nScreenHeight);
    HGDIOBJ hOld = SelectObject(hCaptureDC, hBitmap);
    BitBlt(hCaptureDC, 0, 0, nScreenWidth, nScreenHeight, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
    SelectObject(hCaptureDC, hOld); // always select the previously selected object once done
    DeleteDC(hCaptureDC);
    return hBitmap;
}

int main() {
    HWND appHWND = FindWindowA(NULL, "Hello World!");
    HDC hdc = GetDC(appHWND);
    HBITMAP hBitmap = GetScreenBmp(hdc);

    BITMAPINFO MyBMInfo = { 0 };
    MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);

    // Get the BITMAPINFO structure from the bitmap
    if (0 == GetDIBits(hdc, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS)){
        cout << "error" << endl;
    }

    // create the bitmap buffer
    BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];

    // Better do this here - the original bitmap might have BI_BITFILEDS, which makes it
    // necessary to read the color table - you might not want this.
    MyBMInfo.bmiHeader.biCompression = BI_RGB;

    // get the actual bitmap buffer
    if (0 == GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS)) {
        cout << "error2" << endl;
    }

    for (int i = 0; i < 100; i++) {
        printf("%d\t", (int)lpPixels[i]);
    }

    DeleteObject(hBitmap);
    ReleaseDC(NULL, hdc);
    delete[] lpPixels;
    return 0;
}
thehorseisbrown
  • 390
  • 2
  • 14
  • 2
    Please post all the relevant code. – 500 - Internal Server Error Nov 19 '18 at 17:17
  • I don't find it at all unusual that the first 25 pixels of the bottom line of a Notepad client window would be all white. You do know that bitmaps are bottom up, right? – Mark Ransom Nov 19 '18 at 19:19
  • Ok, I've just made the notepad window very small and I get a lot of (0,0,0,0) - black transparent and (255,255,255,0) white transparent pixels... However, some normal pixels showed up with 255 as alpha: (240,240,240,255) - this, I suspect, is the scroll bar. I think I should be checking some bounds then... Any idea how? – thehorseisbrown Nov 19 '18 at 19:30
  • 1
    Bitmaps typically ignore the alpha channel. There are only one or two API calls that can make use of them. I certainly wouldn't put any meaning on the values you get back from a DC. If you want to do bounds checking I would use the window client area. – Mark Ransom Nov 19 '18 at 20:44
  • I think you're right. When I do 1000x1000 screen capture using the notepad handle, it draws the app without the app's top menu and the rest is black. The screenshot of the electron app I want is black completely. I think I might have to access some child "render" window... Thank you for your help:) – thehorseisbrown Nov 19 '18 at 20:51
  • Good point about the child window. Notepad for example has an edit control that contains all the content. You can see the window structure with Spy++. – Mark Ransom Nov 20 '18 at 16:08

1 Answers1

2

GetDC can be used to take screen shot of the client area in native Win32 applications only. That includes programs like Notepad. Or you can use GetWindowDC to take screenshot of the whole window.

But applications made with frameworks such as electron app, QT, WPF... will print black screen in response to GetDC or GetWindowDC. The only way around it is to make sure the target application is visible and take a screenshot of desktop at the specific coordinates where the target application is at.

Windows GDI functions generally ignore alpha channel. But if you retrieve the screenshot in 32-bit, then GetDIBits will set the all alpha values set to 255 (at least in Windows 10).


Side note,

Your code has a resource leak because it's calling ReleaseDC with the wrong window handle. If you called GetDC(NULL) then you end it with ReleaseDC(NULL, hdc), otherwise it should be corrected as follows:

HDC hdc = GetDC(appHWND);
...
//ReleaseDC(NULL, hdc); <- wrong window handle
ReleaseDC(appHWND, hdc);

You can also save the whole image instead of printing one byte at a time. Example:

#include <fstream>
#include <vector>
#include <windows.h>

int main() 
{
    //make sure process is DPI aware
    SetProcessDPIAware();

    HWND hwnd_target = FindWindowA("Notepad", NULL);
    if(!hwnd_target)
        return 0;

    //make sure target window is on top
    SetForegroundWindow(hwnd_target);
    Sleep(250);

    //get hdc of desktop
    HDC hdc = GetDC(HWND_DESKTOP);

    //copy bits from coordinates of target window
    RECT rc;
    GetWindowRect(hwnd_target, &rc);
    int w = rc.right - rc.left;
    int h = rc.bottom - rc.top;
    HDC memdc = CreateCompatibleDC(hdc);
    HBITMAP hbitmap = CreateCompatibleBitmap(hdc, w, h);
    HGDIOBJ oldbmp = SelectObject(memdc, hbitmap);
    BitBlt(memdc, 0, 0, w, h, hdc, rc.left, rc.top, SRCCOPY | CAPTUREBLT);
    SelectObject(memdc, oldbmp);
    DeleteDC(memdc);

    //restore the foreground
    SetForegroundWindow(GetConsoleWindow());

    //save to file
    BITMAPINFOHEADER bi = { sizeof(bi), w, h, 1, 32 };
    std::vector<BYTE> pixels(w * h * 4);
    GetDIBits(hdc, hbitmap, 0, h, pixels.data(), 
        (BITMAPINFO*)&bi, DIB_RGB_COLORS);

    std::ofstream fout("filename.bmp", std::ios::binary);
    BITMAPFILEHEADER hdr = { 'MB', 54 + bi.biSizeImage, 0, 0, 54 };
    fout.write((char*)&hdr, 14);
    fout.write((char*)&bi, 40);
    fout.write((char*)pixels.data(), pixels.size());

    DeleteObject(hbitmap);
    ReleaseDC(HWND_DESKTOP, hdc);

    return 0;
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77