【问题标题】:BitBlt - captured pixels are all zero (bgra) when using HDC of another appBitBlt - 使用另一个应用程序的 HDC 时捕获的像素全为零 (bgra)
【发布时间】:2019-04-22 02:22:46
【问题描述】:

感谢 Nick Nougat 的回答 here 中的代码,我可以使用 BitBlt 和 GetDIBits 成功捕获部分屏幕。

捕获整个屏幕或桌面似乎可行,但是当我提供应用程序的 HDC 时,它会打印出奇怪的数据(bgra 格式)。

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表示电子应用支持BitBlt和 设备TECHNOLOGYraster display
  • 应用 DC 的宽度始终为全屏,与窗口大小无关:printf("width %d\n", GetDeviceCaps(GetDC(appHWND), HORZRES)); //1920,这是正确的行为吗?

我对 Windows API 非常陌生...steps 或函数中的哪一个可能导致此问题? 谢谢。

....

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;
}

【问题讨论】:

  • 请贴出所有相关代码。
  • 记事本客户端窗口底行的前 25 个像素全为白色,这并不奇怪。你知道位图是自下而上的,对吧?
  • 好的,我刚刚把记事本窗口做得很小,我得到了很多 (0,0,0,0) - 黑色透明和 (255,255,255,0) 白色透明像素...但是,一些正常像素以 255 作为 alpha 显示:(240,240,240,255) - 我怀疑这是滚动条。我想我应该检查一些界限......知道怎么做吗?
  • 位图通常会忽略 Alpha 通道。只有一两个 API 调用可以使用它们。我当然不会对您从 DC 获得的值赋予任何意义。如果你想做边界检查,我会使用窗口客户区。
  • 我认为你是对的。当我使用记事本句柄进行 1000x1000 屏幕捕获时,它会在没有应用程序顶部菜单的情况下绘制应用程序,其余部分为黑色。我想要的电子应用程序的屏幕截图完全是黑色的。我想我可能需要访问一些子“渲染”窗口...谢谢您的帮助:)

标签: windows winapi


【解决方案1】:

GetDC 仅可用于在本机 Win32 应用程序中截取客户区的屏幕截图。这包括像记事本这样的程序。或者你可以使用GetWindowDC截取整个窗口。

但是使用electron app、QT、WPF等框架制作的应用程序会打印黑屏以响应GetDCGetWindowDC。唯一的解决方法是确保目标应用程序是可见的,并在目标应用程序所在的特定坐标处截取桌面。

Windows GDI 函数通常会忽略 Alpha 通道。但是,如果您以 32 位检索屏幕截图,则 GetDIBits 会将所有 alpha 值设置为 255(至少在 Windows 10 中)。


边注,

您的代码存在资源泄漏,因为它使用错误的窗口句柄调用 ReleaseDC。如果调用GetDC(NULL),则以ReleaseDC(NULL, hdc)结束,否则应更正如下:

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

您还可以保存整个图像,而不是一次打印一个字节。示例:

#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;
}

【讨论】:

    猜你喜欢
    • 2012-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-27
    • 1970-01-01
    • 1970-01-01
    • 2020-02-21
    • 2013-06-28
    相关资源
    最近更新 更多