How can I take a screenshot in a windows application?
Asked Answered
T

5

62

How can I take a screenshot of the current screen using Win32?

Trahan answered 20/7, 2010 at 14:46 Comment(3)
Various methods for capturing the screen codeproject.com/Articles/5051/…Stover
Here's my compilable gist: gist.github.com/rdp/9821698Gerenuk
Press Prt-scr on your keyboard ;)Iyar
A
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);
Apiary answered 20/7, 2010 at 15:7 Comment(13)
This works on all nt based windows from Windows NT4 to Windows 7.Apiary
Why are you using CreateDC and not just GetDC(NULL)?Charil
Honestly I haven't looked at it for a while, this is code from quite a way back which I have been using in an application. It works in everything so I have never gone back to it! If GetDC would be better, I can ammend the answer.Apiary
This isn't a good example at all for someone coming into this stuff. The variable aren't declared, the functions don't match the supposed data types, and there are simple syntax errors like missing semicolons. I cannot fix this as I am trying to learn this myself, but this really needs to be updated.Dowry
@Dowry I've attempted to fix the lack of variable declarations and missing semicolon (edit awaiting peer review). Which are the non-matching data types?Llamas
You might also want CAPTUREBLT in the BitBlt ROP parameter. It's necessary when capturing a window, but maybe not when capturing the entire screen.Koreykorff
@Apiary for some reason, this gives me black screen for some games, like League of legends. Are there any workarounds?Renayrenckens
@TomášZato - I am afraid I don't know, I haven't tried, or had to do anything like that for many years. My guess would be that it is rendered into a 3d context outside the device context, but that is just a guessApiary
@Apiary Any idea what to google for? I'm really lost with this, I don't even know where to start...Renayrenckens
@TomášZato - I really don't know, not something I have tried. I am not familiar with the game but have you tried something like this: https://mcmap.net/q/323445/-opengl-game-screen-captureApiary
What is width and height? I guess it's x and y?Kimber
On win 10 I have a memory leak with this example. The example of pcunite works fine.Burne
Windows documentation for DeleteDC says that you shall call ReleaseDC instead of DeleteDC for a device context handle obtained by GetDC. (learn.microsoft.com/en-us/windows/win32/api/wingdi/…)Blood
P
35
  1. Use GetDC(NULL); to get a DC for the entire screen.
  2. Use CreateCompatibleDC to create a DC compatible with the screen DC.
  3. Use CreateCompatibleBitmap to create a bitmap compatible with the screen DC to hold the result.
  4. Use SelectObject to select the compatible bitmap into the compatible DC.
  5. Use BitBlt to copy from the screen DC to the compatible DC.
  6. Use SelectObject to deselect the compatible bitmap from the compatible DC.
  7. Use DeleteDC to delete the compatible DC.

When you create the compatible bitmap, you want it compatible with the screen DC, not the compatible DC.

For example:

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);

The other important part is to get the size, and location, of the entire virtual screen:

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)
Pennsylvanian answered 20/7, 2010 at 14:53 Comment(3)
What about dual display systems? Shot of both screens?Oenomel
What's the difference between DC compatible with screen vs compatible with the other compatible DC?Baloney
@Ayxan It's about the compatible bitmap, not the compatible DC. When you create a compatible DC, it contains the most "economical" bitmap possible--a 1x1 monochrome bitmap. If you create a bitmap compatible with that DC, you get a monochrome bitmap.Pennsylvanian
P
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);
}
Pilferage answered 31/1, 2015 at 5:32 Comment(1)
This example will not work correctly if there is a display above or to the left of the primary display, such that x1!=0 || x2!=0. This is because SM_CXVIRTUALSCREEN and SM_CYVIRTUALSCREEN are the size, not the bottom-right extents. Should just set w,h from them directly. See MSDNNuthouse
D
11

Full code for saving a raw 24-bit bitmap of all monitors at the current window station using Windows API:

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;
}
Darlenadarlene answered 21/11, 2018 at 22:48 Comment(2)
OK, I see! Though it probably does not allocate anything (were it, you would have to free it).Wenwenceslaus
Can I ask why we can't just call GetSystemMetrics(SM_CXVIRTUALSCREEN) and GetSystemMetrics(SM_CYVIRTUALSCREEN) to get lWidth and lHeight? @GovindParmarPony
C
5

There is a MSDN sample, Capturing an Image, for capturing an arbitrary HWND to a DC (you could try passing the output from GetDesktopWindow to this). But how well this will work under the new desktop compositor on Vista/Windows 7, I don't know.

Cerement answered 20/7, 2010 at 14:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.