使用 Bitblt 更快地读取像素颜色

问题描述 投票:0回答:2

我正在开发一个项目,我必须使用 CreateCompatibleBitmap 和 Bitblt 读取屏幕上像素的颜色。不幸的是,这个方法非常慢,我在一个循环中得到了大约 60 到 100 毫秒的时间。我使用这个代码:

HDC hdc = GetDC(NULL), hdcMem = CreateCompatibleDC(hdc);
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, ScreenX, ScreenY);
BITMAPINFOHEADER bmi = { 0 };
bmi.biSize = sizeof(BITMAPINFOHEADER);
bmi.biPlanes = 1;
bmi.biBitCount = 24;
bmi.biWidth = ScreenX;
bmi.biHeight = -ScreenY;
bmi.biCompression = BI_RGB;
SelectObject(hdcMem, hBitmap);
BitBlt(hdcMem, 0, 0, ScreenX, ScreenY, hdc, 0, 0, SRCCOPY);
GetDIBits(hdc, hBitmap, 0, ScreenY, ScreenData, (BITMAPINFO*)&bmi, DIB_RGB_COLORS);
DeleteObject(hBitmap);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdc);

大部分时间(95%)花在 Bitblt 函数上。我读到它需要很长时间,因为 Bitblt 必须转换颜色格式,但我不明白如何避免这种情况......

我使用 Windows 11,屏幕分辨率为 FHD 1920x1080

有什么建议可以加快这个程序的速度吗?

c++ colors bitblt
2个回答
1
投票

您可以使用

CreateDIBSection
创建一种位图,让您可以访问像素数据,而不是创建常规的“兼容”位图,然后使用
GetDIBits
将像素数据复制到缓冲区中,但我运行了一个基准测试这样可以节省时间,但并不多。我的速度提高了大约 10%,但这并不多。

下面的代码,我写得很快,所以可能犯了一个错误。下面的变量

dummy
的目的是让一切都不会因为没有输出而被优化掉。

#include <vector>
#include <iostream>
#include <Windows.h>
#include <chrono>

namespace c = std::chrono;

void get_screen_bytes1(void* ScreenData, int ScreenX, int ScreenY) {
    HDC hdc = GetDC(NULL), hdcMem = CreateCompatibleDC(hdc);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdc, ScreenX, ScreenY);
    BITMAPINFOHEADER bmi = { 0 };
    bmi.biSize = sizeof(BITMAPINFOHEADER);
    bmi.biPlanes = 1;
    bmi.biBitCount = 24;
    bmi.biWidth = ScreenX;
    bmi.biHeight = -ScreenY;
    bmi.biCompression = BI_RGB;
    SelectObject(hdcMem, hBitmap);
    BitBlt(hdcMem, 0, 0, ScreenX, ScreenY, hdc, 0, 0, SRCCOPY);
    GetDIBits(hdc, hBitmap, 0, ScreenY, ScreenData, (BITMAPINFO*)&bmi, DIB_RGB_COLORS);
    DeleteObject(hBitmap);
    DeleteDC(hdcMem);
    ReleaseDC(NULL, hdc);
}

struct bitmap_info {
    HBITMAP handle;
    uint8_t* data;
};

bitmap_info get_screen_bytes2(int wd, int hgt) {
    HDC hdc_scr = GetDC(NULL);
    BITMAPINFO bmi;
    memset(&bmi, 0, sizeof(BITMAPINFO));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = wd;
    bmi.bmiHeader.biHeight = -hgt; 
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 24;
    bmi.bmiHeader.biCompression = BI_RGB;

    bitmap_info bi;
    bi.handle = CreateDIBSection(hdc_scr, &bmi, DIB_RGB_COLORS, (void**)bi.data, NULL, NULL);

    HDC hdc = CreateCompatibleDC(hdc_scr);
    auto hbm_old = SelectObject(hdc, bi.handle);
    BitBlt(hdc, 0, 0, wd, hgt, hdc_scr, 0, 0, SRCCOPY);
    SelectObject(hdc, hbm_old);
    DeleteDC(hdc);
    ReleaseDC(NULL, hdc_scr);

    return bi;
}

int main() {
    constexpr auto scr_wd = 2560;
    constexpr auto scr_hgt = 1440;

    std::vector<uint8_t> buffer(scr_wd * scr_hgt * 3);
    std::chrono::high_resolution_clock timer;

    int dummy;
    double sum = 0.0;
    int n = 100;
    for (int i = 0; i < n; ++i) {
        auto start = timer.now();
        get_screen_bytes1(buffer.data(), scr_wd, scr_hgt);
        sum += c::duration_cast<c::microseconds>(timer.now() - start).count();
        dummy += buffer[0];
    }
    double time1 = sum / n;
    std::cout << "get_screen_bytes1 => " << time1 << "\n";

    sum = 0.0;
    for (int i = 0; i < n; ++i) {
        auto start = timer.now();
        auto bmp_info = get_screen_bytes2( scr_wd, scr_hgt);
        sum += c::duration_cast<c::microseconds>(timer.now() - start).count();
        dummy += bmp_info.data[0];
        DeleteObject(bmp_info.handle);
    }
    double time2 = sum / n;
    std::cout << "get_screen_bytes2 => " << time2 << "\n";
    std::cout << dummy << "\n";
    std::cout << "pcnt speed improvement => " << time2 / time1 << "\n";

}

0
投票

解决方案非常简单:除非您确实需要捕获整个屏幕,否则不要使用

GetDC(NULL)
。从窗口读取像素的成本远低于从屏幕

HWND hwnd = FindWindow(nullptr, TEXT("Your window name")); // or HWND hwnd = GetForegroundWindow() or another way to get the needed HWND

然后:

HDC hdc = GetDC(hwnd);

在我的电脑上,速度提高了大约 50 倍。

不过,值得注意的是,这么快

BitBlt
并不是没有成本的。虽然每个
BitBlt
的成本降低了,但由于调用更加频繁,会消耗大量的GPU资源。所以,请适当限制其速度,因为多次捕获同一帧是没有意义的。

如果您想要捕获的窗口无法使用 BitBlt,请尝试禁用硬件加速、关闭专用显卡或切换目标窗口以使用 DirectX 渲染。或者,您可以考虑使用 Windows.Graphics.Capture API。然而,这比 GDI 方法复杂得多。

奖励喋喋不休:在任务管理器中,BitBlt 的 GPU 消耗不会归因于调用进程。相反,对 BitBlt 使用 GetDC(NULL) 会增加 dwm.exe 的 GPU 消耗,而对 BitBlt 使用 GetDC(hwnd) 会增加 csrss.exe.

的 GPU 消耗
© www.soinside.com 2019 - 2024. All rights reserved.