5

I've a function to take a screenshot on windows platforms with the WinApi and C++. It works perfectly with one and two monitors but when I run it on a machine with 3 monitors or more it only takes the picture of two monitors.

I think my poblem is that the content of the monitor to the left of the "main" monitor gets cut of. Sadly I have no idea how to fix it or what I am doing wrong.

I have read about BitBlt & StretchBlt so I tryed it with them but no success.

This it's what I'm doing:

// Get the system metrics
const int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
const int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);

The width & height gets calculated perfectly for all monitors.

// Create a normal DC and a memory DC for the entire screen. The normal DC provides a "snapshot" of the screen contents.
// The memory DC keeps a copy of this "snapshot" in the associated bitmap.
const HDC hdcScr = CreateDCW(TEXT("DISPLAY"), NULL, NULL, NULL);
const HDC hdcMem = CreateCompatibleDC(hdcScr);



if (!StretchBlt(hdcMem, 0, 0, width, height, hdcScr, 0, 0, width, height, SRCCOPY)) {
    return;
}


// Create a compatible bitmap for hdcScreen.
const HBITMAP hbmScr = CreateCompatibleBitmap(hdcScr, width, height);
if (hbmScr == 0) {
    return;
}

if (!BitBlt(hdcMem, 0, 0, width, height, hdcScr, 0, 0, SRCCOPY)) {
    return;
}



// Select the bitmaps into the compatible DC.
if (!SelectObject(hdcMem, hbmScr)) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    DeleteObject(hbmScr);
    return;
}

// Copy color data for the entire display into a bitmap that is selected into a compatible DC.
if (!StretchBlt(hdcMem, 0, 0, width, height, hdcScr, 0, 0, width, height, SRCCOPY)) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    DeleteObject(hbmScr);
    return;
}

BITMAP bmp;

// Retrieve the bitmap's color format, width, and height.
if (!GetObject(hbmScr, sizeof(BITMAP), reinterpret_cast<LPSTR>(&bmp))) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    DeleteObject(hbmScr);
    return;
}


// Convert the color format to a count of bits.
unsigned short cClrBits = bmp.bmPlanes * bmp.bmBitsPixel;
if (cClrBits == 1) {
    cClrBits = 1;
}
else if (cClrBits <= 4) {
    cClrBits = 4;
}
else if (cClrBits <= 8) {
    cClrBits = 8;
}
else if (cClrBits <= 16) {
    cClrBits = 16;
}
else if (cClrBits <= 24) {
    cClrBits = 24;
}
else {
    cClrBits = 32;
}


PBITMAPINFO pbmi;

// Allocate memory for the BITMAPINFO structure. (This structure contains a BITMAPINFOHEADER structure and an array of RGBQUAD data structures.)
if (cClrBits != 24) {
    pbmi = static_cast<PBITMAPINFO>(LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * (1 << cClrBits)));
}
else { // There is no RGBQUAD array for the 24-bit-per-pixel format.
    pbmi = static_cast<PBITMAPINFO>(LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER)));
}

// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;

if (cClrBits < 24) {
    pbmi->bmiHeader.biClrUsed = (1 << cClrBits);
}

// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;

// Compute the number of bytes in the array of color indices and store the result in biSizeImage.
pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) / 8 * pbmi->bmiHeader.biHeight * cClrBits;

// Set biClrImportant to 0, indicating that all of the device colors are important.
pbmi->bmiHeader.biClrImportant = 0;


const PBITMAPINFOHEADER pbih = reinterpret_cast<PBITMAPINFOHEADER>(pbmi);              // bitmap info-header
const LPBYTE lpBits = static_cast<LPBYTE>(GlobalAlloc(GMEM_FIXED, pbih->biSizeImage)); // memory pointer

if (!lpBits) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    LocalFree(pbmi);
    GlobalFree(lpBits);
    DeleteObject(hbmScr);
    return;
}

// Retrieve the color table (RGBQUAD array) and the bits (array of palette indices) from the DIB.
if (!GetDIBits(hdcMem, hbmScr, 0, pbih->biHeight, lpBits, pbmi, DIB_RGB_COLORS)) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    LocalFree(pbmi);
    GlobalFree(lpBits);
    DeleteObject(hbmScr);
    return;
}

BITMAPFILEHEADER hdr; // bitmap file-header

hdr.bfType = 0x4d42; // ('M' << 8) + 'B';

// Calculate the size of the entire file.
hdr.bfSize = sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage;
hdr.bfReserved1 = NULL;
hdr.bfReserved2 = NULL;

// Calculate the offset to the array of color indices.
hdr.bfOffBits = sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);


const DWORD cb = pbih->biSizeImage; // incremental count of bytes


std::stringstream outputBitmap;

// Write the BITMAPFILEHEADER into the .BMP file.
outputBitmap.write( reinterpret_cast<LPSTR>(&hdr), sizeof(BITMAPFILEHEADER));

// Write the BITMAPINFOHEADER and RGBQUAD array into the file.
outputBitmap.write(reinterpret_cast<LPSTR>(pbih), sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof(RGBQUAD));

// Write the array of color indices
outputBitmap.write(reinterpret_cast<LPSTR>(lpBits), cb);




// To test the whole thing
std::ofstream out("test.bmp", std::ios::out | std::ios::binary);
out << outputBitmap.str();
out.close();


// Cleanup
DeleteDC(hdcScr);
DeleteDC(hdcMem);
LocalFree(pbmi);
GlobalFree(lpBits);
DeleteObject(hbmScr);
The Coder
  • 143
  • 8

1 Answers1

7

Monitors can have negative coordinates, so it's dangerous to assume that (0, 0) is the monitor's top left corner. The real origin (x, y) is given by the system metrics SM_XVIRTUALSCREEN and SM_YVIRTUALSCREEN. Then all of your BLTs have to be updated to refer to the correct source location. This makes the first bit of the function:

int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
int w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int h = GetSystemMetrics(SM_CYVIRTUALSCREEN);

BOOL ok = StretchBlt(hdcMem, 0, 0, w, h, hdcScr, x, y, w, h, SRCCOPY);
// And So On...

Alternatively, since these kind of questions get asked a lot, you can get the individual monitors using something similar to the following piece of not-quite-production-quality code:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <iostream>
using std::wcout;
using std::endl;

typedef struct tagMonData
{
    int current;
    MONITORINFOEXW* info;
} MonData;

BOOL EnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    MonData* data = (MonData*)dwData;
    data->info[data->current].cbSize = sizeof(MONITORINFOEXW);
    return GetMonitorInfoW(hMonitor, &(data->info[data->current++]));
}

BOOL GetAllMonitorInfo(MonData* data)
{
    return EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)(&EnumProc), (LPARAM)(data));
}

int main()
{
    int cMonitors = GetSystemMetrics(SM_CMONITORS);
    MonData data;
    data.current = 0;
    data.info = (MONITORINFOEXW*)calloc(cMonitors, sizeof(MONITORINFOEXW));

    if (!GetAllMonitorInfo(&data)) return 1;

    for (int i = 0; i < cMonitors; i++)
    {
        wcout << data.info[i].szDevice << "X: " << data.info[i].rcMonitor.left << " Y: " << data.info[i].rcMonitor.top << endl;
    }

    free(data.info);
    return 0;
}

Note that this will shortcut and leave incomplete information if the GetMonitorInfoW call fails for some reason.


Raymond Chen has written several articles about the complexities of the Windows coordinate system over at The Old New Thing

theB
  • 6,450
  • 1
  • 28
  • 38