Reputation:
I am attempting to write a C++ program that involves processing data captured from a window. I am intentionally trying to write this myself using windows for the experience, so please do not recommend libraries that already exist to make this task easier. Anyway, for the sake of testing, I have written a function that should accept a hwnd handle and an hdc device context, and use that information to create a bitmap image file of the client area of the window reference by hwnd. The code I have written is heavily based on this sample code from MSDN. I am just trying to capture the data for now, not display it to the screen or modify it in any way. I have also heavily commented the code to give an idea of what exactly it is I think I am doing with each line. Please let me know if I have any fundamental misunderstandings here.
void screenshot(HWND hwnd, HDC hdc){
HDC clientarea = hdc; // The device context for the client area of the window
HDC memory = NULL; // client context for storing the bitmap in memory
RECT clientdim; // Defines memeory location for dimensions of client window
GetClientRect(hwnd, &clientdim); // Stores the client dimesnion info at clientdim location
BITMAP img; // Points to the where the bitmap handle will write it's data once it contains the bitmap data for the client window
HBITMAP clienthandle = CreateCompatibleBitmap(clientarea, clientdim.right - clientdim.left, clientdim.bottom - clientdim.top); // Bitmap handle, actual data of the rectangle defined at clientdim
SelectObject(memory, clienthandle); // Selecting the bitmap handle into the memory context allows us to bitblit the bitmap data to the bitmap handle
BitBlt(memory, 0, 0, clientdim.right - clientdim.left, clientdim.bottom - clientdim.top, clientarea, 0, 0, SRCCOPY);
// The bitmap handle now contains the data defined by the rectangle coordinates, which the dimensions of the hwnd client area
// The source and desitnation nXdest, nYdest, etc. fields are with respect the the window, not the screen, so they are 0
// Gets the bitmap data from the handle, and stores it to a memory location as an actual bitmap file
GetObject(clienthandle, sizeof(BITMAP), &img);
// Info head for the bitmap. Information about the dimesions and colors
BITMAPINFOHEADER bmih;
bmih.biSize = sizeof(BITMAPINFOHEADER); // Sets the size of the header literally to the size of an info header.
bmih.biWidth = img.bmWidth; // Sets the width of the bitmap to the width of the bitmap in memory
bmih.biHeight = img.bmHeight; // Sets the height of the bitmap to the height of the bitmap in memory
bmih.biPlanes = 1; // number of planes must be 1 for bitmaps to save
bmih.biBitCount = 32; // 32-bit color. Means RGB dat is stored per pixel.
bmih.biCompression = BI_RGB; // Tells it to use the RGB compression, as defined by the 32bit pixels
bmih.biSizeImage = 0; // Size of compression buffer. RGB is uncompressed, so is 0 for now
bmih.biXPelsPerMeter = 0; // Defines the number of pixels per meter in the x axis. Only used by some programs to select images, we don't care.
bmih.biYPelsPerMeter = 0; // Same as above, but fore y axis.
bmih.biClrUsed = 0; // the number of color indexes the bitmat uses. 0 means it uses all of them
bmih.biClrImportant = 0; // The number of color indexes actually required to display a pixel. 0 means we need all of them.
DWORD bitmapsize = ((img.bmWidth * bmih.biBitCount + 31) / 32) * 4 * img.bmHeight; // Total size of the bitmap. Not sure why this works, will look into it
HANDLE hDIB = GlobalAlloc(GHND, bitmapsize); // A handle to heap memory of size bitmapsize where our bitmap is processed. GHND = Initializes memory to zero an
//allows location to translate to pointer with globallock, called such because it locks the memory in place within the heap
// hDIB because it is a Device Independent Bitmap
char *lpbitmap = (char *)GlobalLock(hDIB); // Stores the contents of the char at the memory locattion defined by GlobalLock(). Basically, this is the bitmap in the heap.
GetDIBits(clientarea, clienthandle, 0, (UINT)img.bmHeight, lpbitmap, (BITMAPINFO *)&bmih, DIB_RGB_COLORS); // Gets the bits from the bitmap and copies them to the buffer found at lpbitmap
HANDLE hFile = CreateFile(L"capture.bmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); // Creates file with various attributes. Check the documentation on MSDN.
DWORD dibSize = bitmapsize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); //Size of the dib is the size of the actual bitmap plus the size of its headers
BITMAPFILEHEADER bmfh; // Fileheader for the bitmap file
bmfh.bfType = 0x4D42; // Defines file as bitmap. 0x4D42 is the code for this
bmfh.bfSize = dibSize; // The total size is everything in the DIB
bmfh.bfReserved1 = 0; // These reserved values must be zero
bmfh.bfReserved2 = 0;
bmfh.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER); // Offset to where the image begins. It begins after the header data
// File headers are written first, then the actual bitmap data.
DWORD byteswritten = 0; //Used in writefile commands to accept number of bytes written. I'd have to use an overlapped structure otherwise
WriteFile(hFile, (LPSTR)&bmfh, sizeof(BITMAPFILEHEADER), &byteswritten, NULL); // Look at documentation for remind on what the hell is happening here
WriteFile(hFile, (LPSTR)&bmih, sizeof(BITMAPINFOHEADER), &byteswritten, NULL);
WriteFile(hFile, (LPSTR)lpbitmap, bitmapsize, &byteswritten, NULL);
// Unlcok DIB from the heap, then free it's memory back up
GlobalUnlock(hDIB);
GlobalFree(hDIB);
// Close the file handle
CloseHandle(hFile);
// Delete all of the objects and release the device contexts
DeleteObject(memory);
ReleaseDC(hwnd, clientarea);
// Donezo!!
HDC hdc is the device context of the window indicated by hwnd. I pass both of these to the function instead of just using GetDC(hwnd) in the function itself because I already have the device context from elsewhere in the code. I assume this is more efficient.
Any guidance at all would be very helpful. Thank you.
EDIT: It was pointed out that I forgot the actual question in the initial post... The problem I am having is this code is saving a bitmap that is just a black box. It does not actually capture the contents of the window.
Upvotes: 1
Views: 1791
Reputation: 51
Here is a version that works for me.
void screenshot(HWND hWnd,wchar_t* fileName){
HBITMAP hbmScreen{NULL};
BITMAP bmpScreen;
DWORD dwBytesWritten = 0;
DWORD dwSizeofDIB = 0;
HANDLE hFile = NULL;
char* lpbitmap = NULL;
HANDLE hDIB = NULL;
DWORD dwBmpSize = 0;
// Retrieve the handle to a display device context for the client
// area of the window.
const HDC hdcScreen = GetDC(hWnd);
// Create a compatible DC, which is used in a BitBlt from the window DC.
const HDC hdcMemDC = CreateCompatibleDC(hdcScreen);
if (!hdcMemDC)
{
MessageBox(hWnd, L"CreateCompatibleDC has failed", L"Failed", MB_OK);
goto done;
}
// Get the client area for size calculation.
RECT rcClient;
GetClientRect(hWnd, &rcClient);
// This is the best stretch mode.
SetStretchBltMode(hdcScreen, HALFTONE);
// Create a compatible bitmap from the Window DC.
hbmScreen = CreateCompatibleBitmap(hdcScreen, 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,
hdcScreen,
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;
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.
hDIB = GlobalAlloc(GHND, dwBmpSize);
lpbitmap = (char*)GlobalLock(hDIB);
// Gets the "bits" from the bitmap, and copies them into a buffer
// that's pointed to by lpbitmap.
GetDIBits(hdcScreen, hbmScreen, 0,
(UINT)bmpScreen.bmHeight,
lpbitmap,
(BITMAPINFO*)&bi, DIB_RGB_COLORS);
// A file is created, this is where we will save the screen capture.
hFile = CreateFile(fileName,
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.
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.
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);
ReleaseDC(hWnd, hdcScreen);
}
Upvotes: 2