Reputation: 398
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
...
GetDeviceCaps
says that the electron app supports BitBlt
and
the device TECHNOLOGY
is raster display
.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;
}
Upvotes: 1
Views: 2410
Reputation: 31669
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).
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;
}
Upvotes: 2