57

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

Rakete1111
  • 42,521
  • 11
  • 108
  • 141
user63898
  • 26,293
  • 71
  • 223
  • 438
  • 2
    Various methods for capturing the screen http://www.codeproject.com/Articles/5051/Various-methods-for-capturing-the-screen – hB0 Jul 06 '12 at 11:44
  • 1
    Here's my compilable gist: https://gist.github.com/rdp/9821698 – rogerdpack Mar 27 '14 at 23:45

5 Answers5

67
// get the device context of the screen
HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);     
// and a device context to put it in
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);

int width = GetDeviceCaps(hScreenDC, HORZRES);
int height = GetDeviceCaps(hScreenDC, VERTRES);

// maybe worth checking these are positive values
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);

// get a new bitmap
HBITMAP hOldBitmap = (HBITMAP) SelectObject(hMemoryDC, hBitmap);

BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP) SelectObject(hMemoryDC, hOldBitmap);

// clean up
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);

// now your image is held in hBitmap. You can save it or do whatever with it
jamesdlin
  • 48,496
  • 10
  • 105
  • 134
Woody
  • 4,897
  • 2
  • 18
  • 28
  • This works on all nt based windows from Windows NT4 to Windows 7. – Woody Jul 20 '10 at 15:11
  • 7
    Why are you using CreateDC and not just GetDC(NULL)? – Anders Jul 21 '10 at 03:57
  • 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. – Woody Jul 21 '10 at 08:04
  • 10
    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. – ozdrgnaDiies Jan 03 '13 at 07:21
  • 2
    @ozdrgnaDiies I've attempted to fix the lack of variable declarations and missing semicolon (edit awaiting peer review). Which are the non-matching data types? – JBentley Mar 21 '13 at 21:01
  • 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. – Adrian McCarthy Jan 10 '14 at 22:53
  • @Woody for some reason, this gives me black screen for some games, like League of legends. Are there any workarounds? – Tomáš Zato - Reinstate Monica Mar 23 '15 at 20:57
  • @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 guess – Woody Mar 26 '15 at 10:03
  • @Woody Any idea what to google for? I'm really lost with this, I don't even know where to start... – Tomáš Zato - Reinstate Monica Mar 26 '15 at 11:21
  • 1
    @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: http://stackoverflow.com/questions/23898877/opengl-game-screen-capture – Woody Mar 26 '15 at 12:27
  • What is `width` and `height`? I guess it's `x` and `y`? – Atmocreations May 04 '15 at 15:29
  • On win 10 I have a memory leak with this example. The example of pcunite works fine. – Ilyssis Feb 16 '17 at 20:34
30
  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)
Ian Boyd
  • 220,884
  • 228
  • 805
  • 1,125
Jerry Coffin
  • 437,173
  • 71
  • 570
  • 1,035
  • 2
    What about dual display systems? Shot of both screens? – i486 Jan 12 '16 at 22:23
  • What's the difference between DC compatible with screen vs compatible with the other compatible DC? – Ayxan Haqverdili May 21 '20 at 16:43
  • 1
    @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. – Jerry Coffin May 21 '20 at 16:56
28
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);
}
pcunite
  • 1,147
  • 13
  • 23
  • 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 [MSDN](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724385(v=vs.85).aspx) – Kevin May 13 '18 at 04:05
7

Full code for saving a raw 24-bit lossless 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);
    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;
}
Govind Parmar
  • 18,500
  • 6
  • 49
  • 78
  • @MartinPrikryl I removed the manual allocation of bBits because at the time of posting I didn’t realize that CreateDIBSection allocates it for you – Govind Parmar Jun 22 '19 at 12:45
  • OK, I see! Though it probably does not allocate anything (were it, you would have to free it). – Martin Prikryl Jun 22 '19 at 13:59
  • This code does not work correctly when there is screen to the left of main/primary screen. Saved bitmap is correct size but display on the left of main screen is not captured. We have black area on the right instead. – Wojciech Jakubas Oct 25 '19 at 17:20
  • To make it work just do this: Add these lines before BitBlt... code. `int x = GetSystemMetrics(SM_XVIRTUALSCREEN); int y = GetSystemMetrics(SM_YVIRTUALSCREEN);` And then replace BitBlt line with this one: `BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY);` – Wojciech Jakubas Oct 25 '19 at 17:31
  • @WojciechJakubas Thank you! – Govind Parmar Oct 25 '19 at 17:41
  • @GovindParmar, and what if I wanted to create JPEG compressed file? I tried changing `biHeader.biCompression` to `BI_JPEG` but it fails. – Amogh Feb 15 '20 at 11:30
  • Can I ask why we can't just call GetSystemMetrics(SM_CXVIRTUALSCREEN) and GetSystemMetrics(SM_CYVIRTUALSCREEN) to get lWidth and lHeight? @GovindParmar – Rick May 02 '20 at 15:41
  • @Amogh Obviously changing the image format requires more than changing a simple value in a struct. You're still writing the bitmap file header and bits. This might be a valid topic for a new question. – Govind Parmar Nov 08 '20 at 20:45
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.

Wolf
  • 8,482
  • 7
  • 48
  • 92
Bob Moore
  • 6,298
  • 3
  • 27
  • 42