如何在Windows应用程序中进行截屏?

62

如何使用Win32获取当前屏幕的截图?


2
捕获屏幕的各种方法 http://www.codeproject.com/Articles/5051/Various-methods-for-capturing-the-screen - hB0
1
这是我的可编译Gist:https://gist.github.com/rdp/9821698 - rogerdpack
按下键盘上的 Prt-scr ;) - Jesper Juhl
5个回答

72
HDC hScreenDC = GetDC(nullptr); // CreateDC("DISPLAY",nullptr,nullptr,nullptr);
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int width = GetDeviceCaps(hScreenDC,HORZRES);
int height = GetDeviceCaps(hScreenDC,VERTRES);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC,width,height);
HBITMAP hOldBitmap = static_cast<HBITMAP>(SelectObject(hMemoryDC,hBitmap));
BitBlt(hMemoryDC,0,0,width,height,hScreenDC,0,0,SRCCOPY);
hBitmap = static_cast<HBITMAP>(SelectObject(hMemoryDC,hOldBitmap));
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);

10
你为什么要使用CreateDC而不是只用GetDC(NULL)? - Anders
12
这个例子不太适合初学者。变量没有被声明,函数与所谓的数据类型不匹配,还有简单的语法错误,比如缺少分号。我自己也在学习,所以无法修复这个问题,但这个例子确实需要更新。 - ozdrgnaDiies
2
@ozdrgnaDiies 我已经尝试修复变量声明的缺失和缺少分号的问题(编辑等待同行审查)。哪些是不匹配的数据类型? - JBentley
1
@TomášZato - 我真的不知道,这不是我尝试过的东西。我对这个游戏不熟悉,但你是否尝试过像这样的东西:http://stackoverflow.com/questions/23898877/opengl-game-screen-capture - Woody
1
Windows的DeleteDC文档说明,对于使用GetDC获得的设备上下文句柄,应调用ReleaseDC而不是DeleteDC。 (https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-deletedc) - Holger Strauss
显示剩余8条评论

34
  1. 使用GetDC(NULL);获取整个屏幕的设备上下文(DC)。
  2. 使用CreateCompatibleDC创建一个与屏幕DC兼容的DC。
  3. 使用CreateCompatibleBitmap创建一个与屏幕DC兼容的位图,用于保存结果。
  4. 使用SelectObject将兼容位图选择到兼容DC中。
  5. 使用BitBlt从屏幕DC复制到兼容DC。
  6. 使用SelectObject将兼容位图从兼容DC中取消选择。
  7. 使用DeleteDC删除兼容DC。

在创建兼容位图时,要确保它与屏幕DC兼容,而不是与兼容DC兼容。

例如:

HDC dcScreen = GetDC(0);
HDC dcTarget = CreateCompatibleDC(dcScreen);
HBITMAP bmpTarget = CreateCompatibleBitmap(dcScreen);
HGDIOBJ oldBmp = SelectObject(dcTarget, bmpTarget);
BitBlt(dcTarget, 0, 0, cx, cy, dcDesktop, x, y, SRCCOPY | CAPTUREBLT);
SelectObject(dcTarget, oldBmp);
DeleteDC(dcTarget);
ReleaseDC(dcScreen);

另一个重要的部分是获取整个虚拟屏幕的大小和位置:

int x  = GetSystemMetrics(SM_XVIRTUALSCREEN);  //left (e.g. -1024)
int y  = GetSystemMetrics(SM_YVIRTUALSCREEN);  //top (e.g. -34)
int cx = GetSystemMetrics(SM_CXVIRTUALSCREEN); //entire width (e.g. 2704)
int cy = GetSystemMetrics(SM_CYVIRTUALSCREEN); //entire height (e.g. 1050)

3
双屏系统怎么样?拍摄两个屏幕的画面? - i486
屏幕兼容的 DC 和其他兼容 DC 之间有什么区别? - Ayxan Haqverdili
2
@Ayxan,这是关于兼容位图而不是兼容DC的问题。当您创建一个兼容DC时,它包含最“经济”的位图——一个1x1单色位图。如果您创建与该DC兼容的位图,则会得到一个单色位图。 - Jerry Coffin

29
void GetScreenShot(void)
{
    int x1, y1, x2, y2, w, h;

    // get screen dimensions
    x1  = GetSystemMetrics(SM_XVIRTUALSCREEN);
    y1  = GetSystemMetrics(SM_YVIRTUALSCREEN);
    x2  = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    y2  = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    w   = x2 - x1;
    h   = y2 - y1;

    // copy screen to bitmap
    HDC     hScreen = GetDC(NULL);
    HDC     hDC     = CreateCompatibleDC(hScreen);
    HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);
    HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
    BOOL    bRet    = BitBlt(hDC, 0, 0, w, h, hScreen, x1, y1, SRCCOPY);

    // save bitmap to clipboard
    OpenClipboard(NULL);
    EmptyClipboard();
    SetClipboardData(CF_BITMAP, hBitmap);
    CloseClipboard();   

    // clean up
    SelectObject(hDC, old_obj);
    DeleteDC(hDC);
    ReleaseDC(NULL, hScreen);
    DeleteObject(hBitmap);
}

1
如果主显示器上方或左侧有显示器,使得x1!= 0 || x2!= 0,则此示例将无法正常工作。这是因为SM_CXVIRTUALSCREEN和SM_CYVIRTUALSCREEN是_size_,而不是右下角范围。应该直接从它们设置w,h。请参见[MSDN](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724385(v=vs.85).aspx) - Kevin

9

使用Windows API保存当前窗口站点所有监视器的原始24位无损位图的完整代码:

BOOL WINAPI SaveBitmap(WCHAR *wPath)
{
    BITMAPFILEHEADER bfHeader;
    BITMAPINFOHEADER biHeader;
    BITMAPINFO bInfo;
    HGDIOBJ hTempBitmap;
    HBITMAP hBitmap;
    BITMAP bAllDesktops;
    HDC hDC, hMemDC;
    LONG lWidth, lHeight;
    BYTE *bBits = NULL;
    HANDLE hHeap = GetProcessHeap();
    DWORD cbBits, dwWritten = 0;
    HANDLE hFile;
    INT x = GetSystemMetrics(SM_XVIRTUALSCREEN);
    INT y = GetSystemMetrics(SM_YVIRTUALSCREEN);

    ZeroMemory(&bfHeader, sizeof(BITMAPFILEHEADER));
    ZeroMemory(&biHeader, sizeof(BITMAPINFOHEADER));
    ZeroMemory(&bInfo, sizeof(BITMAPINFO));
    ZeroMemory(&bAllDesktops, sizeof(BITMAP));

    hDC = GetDC(NULL);
    hTempBitmap = GetCurrentObject(hDC, OBJ_BITMAP);
    GetObjectW(hTempBitmap, sizeof(BITMAP), &bAllDesktops);

    lWidth = bAllDesktops.bmWidth;
    lHeight = bAllDesktops.bmHeight;

    DeleteObject(hTempBitmap);

    bfHeader.bfType = (WORD)('B' | ('M' << 8));
    bfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    biHeader.biSize = sizeof(BITMAPINFOHEADER);
    biHeader.biBitCount = 24;
    biHeader.biCompression = BI_RGB;
    biHeader.biPlanes = 1;
    biHeader.biWidth = lWidth;
    biHeader.biHeight = lHeight;

    bInfo.bmiHeader = biHeader;

    cbBits = (((24 * lWidth + 31)&~31) / 8) * lHeight;

    hMemDC = CreateCompatibleDC(hDC);
    hBitmap = CreateDIBSection(hDC, &bInfo, DIB_RGB_COLORS, (VOID **)&bBits, NULL, 0);
    SelectObject(hMemDC, hBitmap);
    BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY);

    
    hFile = CreateFileW(wPath, GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(INVALID_HANDLE_VALUE == hFile)
    {
        DeleteDC(hMemDC);
        ReleaseDC(NULL, hDC);
        DeleteObject(hBitmap);

        return FALSE;
    }
    WriteFile(hFile, &bfHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
    WriteFile(hFile, &biHeader, sizeof(BITMAPINFOHEADER), &dwWritten, NULL);
    WriteFile(hFile, bBits, cbBits, &dwWritten, NULL);
    FlushFileBuffers(hFile);
    CloseHandle(hFile);

    DeleteDC(hMemDC);
    ReleaseDC(NULL, hDC);
    DeleteObject(hBitmap);

    return TRUE;
}

@MartinPrikryl 我删除了对 bBits 的手动分配,因为在发布时我没有意识到 CreateDIBSection 会为您分配它。 - Govind Parmar
当主/主要屏幕左侧有屏幕时,此代码无法正常工作。保存的位图大小正确,但左侧主屏幕上的显示未被捕获。相反,在右侧有黑色区域。 - Wojciech Jakubas
只需按照以下步骤操作即可使其正常工作: 在 BitBlt 代码之前添加以下行。 int x = GetSystemMetrics(SM_XVIRTUALSCREEN); int y = GetSystemMetrics(SM_YVIRTUALSCREEN); 然后将 BitBlt 行替换为此行:BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY); - Wojciech Jakubas
@WojciechJakubas 谢谢! - Govind Parmar
1
24位不会是从HDR显示器捕获的无损截图。 - gman
显示剩余4条评论

5

这里有一个MSDN示例,捕获图像,用于将任意HWND捕获到DC(您可以尝试将GetDesktopWindow的输出传递给它)。但在Vista / Windows 7上的新桌面组合器下,这样做的效果如何,我不知道。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接