Я знаю, что в Интернете есть много публикаций для захвата экрана в Windows с использованием GDI или DirectX. Однако все, что я нашел, сохраняет захваченное изображение в растровое изображение, тогда как вместо этого я хочу сохранить его в буфере. Вот мой код для этого в 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?
Во-первых, насколько мне известно, значение lineSizeSrc в этом коде не всегда корректно, поскольку в зависимости от разрешения экрана некоторые нули могут добавляться к каждой строке данных. Может кто-нибудь, пожалуйста, объясните, когда добавляются нули и как получить правильное значение для
lineSizeSrc
?
Растровый формат требует, чтобы каждая строка начиналась с адреса, кратного 4 байтам. Часто это работает, потому что обычная ширина изображения кратна 4, или потому что размер отдельного пикселя составляет 32 бита (что составляет 4 байта).
Но если вы представляете изображение с необычной шириной (например, шириной 31 пиксель) и используете что-то вроде 24 бит (3 байта) на пиксель, то вам нужно дополнить конец каждой строки так, чтобы следующая строка начиналась кратный 4.
Распространенный способ сделать это — округлить «шаг»:
lineSizeSrc = (resx * BitsPerPixel + 31) / 8;
resx * BitsPerPixel
говорит нам количество битов, необходимых для представления строки. Деление на 8 преобразует биты в байты — вроде. Целочисленное деление усекает любой остаток. Добавляя 31 сначала, мы гарантируем, что усечение дает нам наименьшее кратное 32 битам (4 байта), которое равно или больше, чем количество бит, которое нам нужно. Так lineSizeSrc
количество байтов, необходимое для каждой строки.
Вы должны использовать lineSizeSrc
вместо resx
в расчете, сколько байтов вам нужно.
Во-вторых, возможно ли получить захваченное изображение в разрешении 4K независимо от разрешения монитора, например, путем принудительного вывода видеокарты в разрешении 4K?
Там не простой метод работает во всех случаях. Лучше всего попросить программу выполнить рендеринг в окне размером 4K, даже если видеокарта не находится в этом режиме. Некоторые программы будут поддерживать это, но другие могут сейчас. Посмотрите на документацию для WM_PRINT
а также WM_PRINTCLIENT
Сообщения.
Большинство современных мониторов поддерживают 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
чтобы получить верхний левый угол.