在Windows中进行4K屏幕捕获并直接保存到缓冲区

时间:2016-08-01 17:34:38

标签: c++ c windows gdi screen-capture

我知道网上有很多帖子可以使用GDI或DirectX方法在Windows中进行屏幕捕获。但是,我发现只是将捕获的图像保存到位图,而我想将其保存到缓冲区中。以下是我在GDi方式中执行此操作的代码:

HWND hwind = GetDesktopWindow();
HDC hdc = GetDC(hwind);

uint32_t resx = GetSystemMetrics(SM_CXSCREEN);
uint32_t resy = GetSystemMetrics(SM_CYSCREEN);
uint32_t BitsPerPixel = GetDeviceCaps(hdc, BITSPIXEL);
HDC hdc2 = CreateCompatibleDC(hdc);

BITMAPINFO info;
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info.bmiHeader.biWidth = resx;
info.bmiHeader.biHeight = resy;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biBitCount = BitsPerPixel;
info.bmiHeader.biCompression = BI_RGB;

void *data;
static HBITMAP hbitmap = CreateDIBSection(hdc2, &info, DIB_RGB_COLORS,
    (void**)&data, 0, 0);
SelectObject(hdc2, hbitmap);
BitBlt(hdc2, 0, 0, resx, resy, hdc, 0, 0, SRCCOPY);

uint8_t *ptr = new uint8_t[4 * resx * resy];
uint32_t lineSizeSrc = 4 * resx;     // not always correct
uint32_t linesizeDst = 4 * resx;
for (uint32_t y = 0; y < resy; y++)
    memcpy(ptr + y * lineSizeDst,
        (uint8_t*) data + y * lineSizeSrc,
        lineSizeDst);

DeleteObject(hbitmap);
ReleaseDC(hwind, hdc);
if (hdc2) {
    DeleteDC(hdc2);
}

首先,据我所知,此代码中lineSizeSrc的值并不总是正确的,因为根据屏幕分辨率,可能会在data的每一行添加一些零。任何人都可以解释何时添加零以及如何获得lineSizeSrc的正确值?

其次,无论显示器的分辨率如何,都可以以4K分辨率获取捕获的图像,例如强制显卡以4K分辨率输出?

2 个答案:

答案 0 :(得分:2)

大多数现代显示器都支持32位色,相对简单,因为它不需要调色板。 C++中的示例:

void capture(char* &buffer)
{
    HWND hwnd = GetDesktopWindow();
    HDC hdc = GetDC(hwnd);
    int w = GetSystemMetrics(SM_CXSCREEN);
    int h = GetSystemMetrics(SM_CYSCREEN);
    int BitsPerPixel = GetDeviceCaps(hdc, BITSPIXEL); 
    if (BitsPerPixel = 32)
    {
        HDC memdc = CreateCompatibleDC(hdc); 
        HBITMAP bmp = CreateCompatibleBitmap(hdc, w, h); 
        HGDIOBJ oldbitmap = SelectObject(memdc, bmp); 
        BitBlt(memdc, 0, 0, w, h, hdc, 0, 0, CAPTUREBLT | SRCCOPY); 
        SelectObject(memdc, oldbitmap); 

        DWORD bitsize = w * h * 4; 
        char *bits = new char[bitsize]; 

        DWORD szInfoHdr = sizeof(BITMAPINFOHEADER); 
        BITMAPINFOHEADER bmpInfoHeader = 
        { szInfoHdr, w, h, 1, (WORD)BitsPerPixel, BI_RGB, 0, 0, 0, 0, 0 }; 

        GetDIBits(hdc, bmp, 0, h, bits, (BITMAPINFO*)&bmpInfoHeader, DIB_RGB_COLORS);

        buffer = new char[bitsize + szInfoHdr];
        memcpy(buffer, &bmpInfoHeader, szInfoHdr);
        memcpy(buffer + szInfoHdr, bits, bitsize);
        delete[]bits;
        DeleteObject(bmp);
        DeleteObject(memdc);
    }

    ReleaseDC(hwnd, hdc);
}

您可以通过函数传递buffer。以下代码可用于测试:

case WM_PAINT:
{
    PAINTSTRUCT ps; 
    HDC hdc = BeginPaint(hWnd, &ps);
    char *buffer = 0;

    //capture the screen and save to buffer
    capture(buffer);

    if (buffer)
    {
        //paint the buffer for testing:
        BITMAPINFO* bmpinfo = (BITMAPINFO*)buffer;
        if (bmpinfo->bmiHeader.biBitCount == 32)
        {
            int w = bmpinfo->bmiHeader.biWidth;
            int h = bmpinfo->bmiHeader.biHeight;
            char *bits = buffer + sizeof(BITMAPINFOHEADER);
            HBITMAP hbitmap = CreateDIBitmap(hdc, 
                    &bmpinfo->bmiHeader, CBM_INIT, bits, bmpinfo, DIB_RGB_COLORS);
            HDC memdc = CreateCompatibleDC(hdc);
            SelectObject(memdc, hbitmap);
            BitBlt(hdc, 0, 0, w, h, memdc, 0, 0, SRCCOPY);
        }
        delete[]buffer;
    }
    EndPaint(hWnd, &ps);
}

但请注意,GetSystemMetrics(SM_CXSCREEN)仅返回主监视器的宽度。

您可能需要SM_CXVIRTUALSCREENSM_CYVIRTUALSCREEN来获取多显示器的宽度/高度。使用SM_(X/Y)VIRTUALSCREEN获取左上角。

答案 1 :(得分:2)

  

首先,据我所知,此代码中lineSizeSrc的值并不总是正确的,因为根据屏幕分辨率,可能会在每行数据中添加一些零。任何人都可以解释何时添加零以及如何获得lineSizeSrc的正确值?

位图格式要求每一行都以一个4字节倍数的地址开始。通常,这只是因为常见的图像宽度是4的倍数,或者因为单个像素的大小是32位(即4个字节)。

但是,如果您要表示具有不寻常宽度(例如,31像素宽)的图像并且每像素使用24位(3字节),则需要填充每行的末尾以便下一行line以4的倍数开始。

这样做的一个常见方法是整理“步幅”:

lineSizeSrc = (resx * BitsPerPixel + 31) / 8;

resx * BitsPerPixel告诉我们表示该行所需的位数。除以8将位转换为字节 - 排序。整数除法截断任何余数。首先添加31,我们确保截断给出了32位(4字节)的最小倍数,它等于或大于我们需要的位数。所以lineSizeSrc是每行所需的字节数。

在计算所需的字节数时,您应该使用lineSizeSrc而不是resx

  

其次,无论显示器的分辨率如何,都可以以4K分辨率获取捕获的图像,例如强制显卡以4K分辨率输出?

没有一种简单的,全功能的案例方法。您最好的选择可能是要求程序渲染到4K的窗口,即使图形卡不在该模式下也是如此。有些程序会支持这一点,但其他程序可能会支持。查看WM_PRINTWM_PRINTCLIENT消息的文档。

相关问题