我知道网上有很多帖子可以使用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分辨率输出?
答案 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_CXVIRTUALSCREEN
和SM_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_PRINT
和WM_PRINTCLIENT
消息的文档。