Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

multiple screen capture with MSDN library

I am working on screen capture with multiple display units. As GetDesktopWindow() only gets the handle to primary monitors, I tried to use EnumDisplayMonitors() to do the job.

After reading the MSDN website, I wrote these in the main():

HDC hdc = GetDC(NULL);
EnumDisplayMonitors(hdc, NULL, MyCapScreenEnumProc, 0);
ReleaseDC(NULL, hdc);

And for the "BOOL CALLBACK MyCapScreenEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)" callback function, I copied the sample function "int CaptureAnImage(HWND hWnd)" from MSDN:Capturing an Image and did the following modification:

  1. instead of reading a HWND parameter, I declared it in the function and initialized it with GetDesktopWindow()
  2. removed the codes for stretching the device context
  3. used the parameter hdcMonitor as the device context
  4. used the parameter lprcMonitor for the RECT
  5. added codes for generating unique file names

Here is the full code:

BOOL CALLBACK MyCapScreenEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    HWND hWnd = GetDesktopWindow();
    HDC hdcMemDC = NULL;
    HBITMAP hbmScreen = NULL;
    BITMAP bmpScreen;

    //generate a unique file name for the bitmaps
    static int file_number = 1;
    stringstream ss;
    ss << "all_capture_" << file_number++ << ".bmp";
    string filename = ss.str();
    wstring widestr = wstring(filename.begin(), filename.end());

    // Create a compatible DC which is used in a BitBlt from the window DC
    hdcMemDC = CreateCompatibleDC(hdcMonitor);

    if (!hdcMemDC)
    {
        MessageBox(hWnd, L"CreateCompatibleDC has failed", L"Failed", MB_OK);
        goto done;
    }

    // Get the client area for size calculation
    RECT rcClient = *lprcMonitor;

    // Create a compatible bitmap from the Window DC
    hbmScreen = CreateCompatibleBitmap(hdcMonitor, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);

    if (!hbmScreen)
    {
        MessageBox(hWnd, L"CreateCompatibleBitmap Failed", L"Failed", MB_OK);
        goto done;
    }

    // Select the compatible bitmap into the compatible memory DC.
    SelectObject(hdcMemDC, hbmScreen);

    // Bit block transfer into our compatible memory DC.
    if (!BitBlt(hdcMemDC,
                0, 0,
                rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
                hdcMonitor,
                0, 0,
                SRCCOPY))
    {
        MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK);
        goto done;
    }

    // Get the BITMAP from the HBITMAP
    GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);

    BITMAPFILEHEADER   bmfHeader;
    BITMAPINFOHEADER   bi;

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bmpScreen.bmWidth;
    bi.biHeight = bmpScreen.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = 32;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    DWORD dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;

    // Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that
    // call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc
    // have greater overhead than HeapAlloc.
    HANDLE hDIB = GlobalAlloc(GHND, dwBmpSize);
    char *lpbitmap = (char *)GlobalLock(hDIB);

    // Gets the "bits" from the bitmap and copies them into a buffer
    // which is pointed to by lpbitmap.
    GetDIBits(hdcMonitor, hbmScreen, 0,
              (UINT)bmpScreen.bmHeight,
              lpbitmap,
              (BITMAPINFO *)&bi, DIB_RGB_COLORS);




    // A file is created, this is where we will save the screen capture.
    HANDLE hFile = CreateFile(widestr.c_str(),
                              GENERIC_WRITE,
                              0,
                              NULL,
                              CREATE_ALWAYS,
                              FILE_ATTRIBUTE_NORMAL, NULL);

    // Add the size of the headers to the size of the bitmap to get the total file size
    DWORD dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    //Offset to where the actual bitmap bits start.
    bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);

    //Size of the file
    bmfHeader.bfSize = dwSizeofDIB;

    //bfType must always be BM for Bitmaps
    bmfHeader.bfType = 0x4D42; //BM

    DWORD dwBytesWritten = 0;
    WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);

    //Unlock and Free the DIB from the heap
    GlobalUnlock(hDIB);
    GlobalFree(hDIB);

    //Close the handle for the file that was created
    CloseHandle(hFile);

    //Clean up
    done:
    DeleteObject(hbmScreen);
    DeleteObject(hdcMemDC);
    return TRUE;
}

However, it turns out that it capture the primary screen twice. With the second capture's screen size same as my second monitor's. I don't know what is wrong with the codes. Can anyone point it out or suggest a better way to do the task? Thanks!

like image 968
Samuel Avatar asked Feb 01 '15 09:02

Samuel


1 Answers

You need to BitBlt from monitor coordinates provided to you in lprcMonitor, not from zero point:

// Bit block transfer into our compatible memory DC.
if (!BitBlt(hdcMemDC,
            0, 0,
            rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
            hdcMonitor,
            lprcMonitor->left, lprcMonitor->top, // <<--- !!!
            SRCCOPY))
like image 130
Roman R. Avatar answered Oct 03 '22 22:10

Roman R.