2016-08-01 62 views
2

我知道有在網絡上很多帖子做屏幕捕捉在Windows或者使用GDI或DirectX接近。但是,我發現的所有內容都將捕獲的圖像保存爲位圖,而我想將其保存到緩衝區中。這裏是我的代碼在GDI方式做到這一點:4K屏幕在Windows捕獲並直接保存到緩衝區中

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

首先,據我所知,此代碼中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_PRINTWM_PRINTCLIENT消息的文檔。

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獲取左上角。