【问题标题】:How can I take a screenshot in a windows application?如何在 Windows 应用程序中截取屏幕截图?
【发布时间】:2011-03-18 11:56:00
【问题描述】:

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

【问题讨论】:

标签: c++ winapi screenshot


【解决方案1】:
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);

【讨论】:

  • 这适用于从 Windows NT4 到 Windows 7 的所有基于 nt 的窗口。
  • 你为什么使用 CreateDC 而不仅仅是 GetDC(NULL)?
  • 对于涉足这个领域的人来说,这根本不是一个好例子。变量未声明,函数与假定的数据类型不匹配,并且存在简单的语法错误,例如缺少分号。我无法解决这个问题,因为我正在尝试自己学习,但这确实需要更新。
  • @ozdrgnaDiies 我试图解决缺少变量声明和缺少分号的问题(编辑等待同行评审)。哪些是不匹配的数据类型?
  • @TomášZato - 我真的不知道,我没有尝试过。我对游戏不熟悉,但你有没有尝试过这样的事情:stackoverflow.com/questions/23898877/opengl-game-screen-capture
【解决方案2】:

使用 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);
    WriteFile(hFile, &bfHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
    WriteFile(hFile, &biHeader, sizeof(BITMAPINFOHEADER), &dwWritten, NULL);
    WriteFile(hFile, bBits, cbBits, &dwWritten, NULL);

    CloseHandle(hFile);

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

    return TRUE;
}

【讨论】:

  • @MartinPrikryl 我删除了 bBits 的手动分配,因为在发布时我没有意识到 CreateDIBSection 会为您分配它
  • 好的,我明白了!虽然它可能没有分配任何东西(如果是,你将不得不释放它)。
  • 当主/主屏幕左侧有屏幕时,此代码无法正常工作。保存的位图大小正确,但未捕获主屏幕左侧的显示。我们在右边有黑色区域。
  • 要使其正常工作,只需执行以下操作:在 BitBlt... 代码之前添加这些行。 int x = GetSystemMetrics(SM_XVIRTUALSCREEN); int y = GetSystemMetrics(SM_YVIRTUALSCREEN); 然后将 BitBlt 行替换为这一行:BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY);
  • @WojciechJakubas 谢谢!
【解决方案3】:
  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)

【讨论】:

  • 双显示系统怎么样?两个屏幕的镜头?
  • 兼容屏幕的DC和兼容其他兼容DC有什么区别?
  • @Ayxan 这是关于兼容的位图,而不是兼容的 DC。当您创建一个兼容的 DC 时,它包含可能最“经济”的位图——一个 1x1 单色位图。如果您创建与该 DC 兼容的位图,您将获得单色位图。
【解决方案4】:

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

【讨论】:

    【解决方案5】:
    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);
    }
    

    【讨论】:

    • 如果主显示器上方或左侧有显示器,则此示例将无法正常工作,例如 x1!=0 || x2!=0。这是因为 SM_CXVIRTUALSCREEN 和 SM_CYVIRTUALSCREEN 是 大小,而不是右下角的范围。应该直接从他们那里设置 w,h 。见MSDN
    猜你喜欢
    • 2013-11-26
    • 2011-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多