I would like to get the context of my window into a bitmap. I use the window to draw basic lines with touch. The problem that I'm having is my bitmap is black. Which it is probably due to the fact that I'm not grabbing the device context properly or doing something else wrong.
The functions CreateBitmapInfoStruct and CreateBMPFile are from this MSDN example.
Also note that g_hWnd is a global variable that has the handle of the window for which I want to save the picture.
My end goal is to be able to save the bitmap into a mysql field (BLOB) that I have. This is what my original problem was. Anyway, I started by first trying to create a BMP to file.
I have searched here and in other places. The best solution I found was recommended here following this MSDN example. However, it is not working.
Any help for this specific problem and/or in writing to the bitmap into a blob into mysql table, will be greatly appreciated.
Here is my code:
HDC hDC = GetDC(g_hWnd);
LPRECT rect = (LPRECT)malloc(sizeof(RECT));
GetWindowRect(g_hWnd,rect);
int h = rect->right - rect->left;
int w = rect->bottom - rect->top;
LPRECT rect = (LPRECT)malloc(sizeof(RECT));
GetWindowRect(g_hWnd,rect);
HBITMAP hBmp = CreateCompatibleBitmap(hDC,w,h);
PBITMAPINFO pbmi;
pbmi = CreateBitmapInfoStruct(g_hWnd,hBmp);
CreateBMPFile(g_hWnd, TEXT("c:\\TEMPO\\TestG2.bmp"), pbmi,
hBmp, hDC) ;
ReleaseDC(g_hWnd,hDC);
DeleteObject(hBmp);
DeleteObject(pbmi);
if (rect != nullptr)
free(rect);
EDIT:
The actual answer for capturing the screen (getDC) is to modify this sample in MSDN.
I have modified the sample here (remove the stretch) Note that still uses the goto, which I will remove.
A note about the GOTO... WHile I don't use it, I don't find it to be a problem either. I think too much has been made about the GOTO statement... like if in assembly, we wouldn't do goto (JUMPS)
Here is the code:
void saveBitmap()
{
HDC hdcScreen;
HDC hdcWindow;
HDC hdcMemDC = NULL;
HBITMAP hbmScreen = NULL;
BITMAP bmpScreen;
// Retrieve the handle to a display device context for the client
// area of the window.
hdcScreen = GetDC(NULL);
hdcWindow = GetDC(g_hWnd);
// Create a compatible DC which is used in a BitBlt from the window DC
hdcMemDC = CreateCompatibleDC(hdcWindow);
if(!hdcMemDC)
{
goto done;
}
RECT rcClient;
GetClientRect(g_hWnd, &rcClient);
hbmScreen = CreateCompatibleBitmap(hdcWindow, rcClient.right-rcClient.left,
rcClient.bottom-rcClient.top);
if(!hbmScreen)
{
goto done;
}
SelectObject(hdcMemDC,hbmScreen);
if(!BitBlt(hdcMemDC,
0,0,
rcClient.right-rcClient.left, rcClient.bottom-rcClient.top,
hdcWindow,
0,0,
SRCCOPY))
{
goto done;
}
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(hdcWindow, 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(L"c:\\tempo\\captureqwsx.bmp",
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);
ReleaseDC(NULL,hdcScreen);
ReleaseDC(g_hWnd,hdcWindow);
}
You can use GetDIBits().
This function copies the image pixels data into your own allocated memory.
See GetDIBits and loop through pixels using X, Y for some more details and explanation.
Related
I have an HBITMAP holding the screenshot of a window. Now I want to crop a certain region/rectangle out of it and return it as a new HBITMAP.
The following code however only squeezes the image into the correct new rectangle size but does not crop it:
HBITMAP crop_image(const RECT rectangle, const HBITMAP source_image)
{
const auto h_clone = static_cast<HBITMAP>(CopyImage(source_image, IMAGE_BITMAP, rectangle.right - rectangle.left,
rectangle.bottom - rectangle.top, LR_CREATEDIBSECTION));
const auto hdc_mem = CreateCompatibleDC(nullptr);
const auto hdc_mem2 = CreateCompatibleDC(nullptr);
const auto h_old_bmp = static_cast<HBITMAP>(SelectObject(hdc_mem, source_image));
const auto h_old_bmp2 = static_cast<HBITMAP>(SelectObject(hdc_mem2, h_clone));
BitBlt(hdc_mem2, 0, 0, rectangle.right - rectangle.left, rectangle.bottom - rectangle.top,
hdc_mem, rectangle.left, rectangle.top, SRCCOPY);
SelectObject(hdc_mem, h_old_bmp);
SelectObject(hdc_mem2, h_old_bmp2);
DeleteDC(hdc_mem);
DeleteDC(hdc_mem2);
return h_clone;
}
How can I fix my code to crop the image as desired?
I'm not sure if this going to work or not due to I haven't been working on Win32 for a long time, so let's try.
Here is the idea:
Create a new memory DC.
Create a new bitmap instead of cloning from the source.
BitBlt the target rectangle of the source bitmap into the one that just created.
Create a new DC and bitmap
Use CreateCompatibleDC as you did to create a memory DC. Then create a new bitmap with CreateCompatibleBitmap. The width and height of the bitmap will be the size of the cropped bitmap. Then select it into the DC with SelectObject.
Copy the target rectangle from the source
Now you need to create a new DC for the source bitmap with CreateCompatibleDC and select the source bitmap into it with SelectObject. Then use BitBlt to copy the rectangle of the source bitmap into the bitmap that you just created. The destination x and y will be zero because we want to start drawing from the upper-left. The source x and y is where you want to start copy from the source bitmap.
Your code can be reduced to this:
HBITMAP crop_image(const RECT rectangle, const HBITMAP source_image)
{
const auto h_clone = static_cast<HBITMAP>(CopyImage(source_image, IMAGE_BITMAP, rectangle.right - rectangle.left,
rectangle.bottom - rectangle.top, LR_CREATEDIBSECTION));
return h_clone;
}
The deleted code doesn't do anything useful. After I test, I can get the cropped area, not the reduced screenshot. Because you didn't provide the complete code, I couldn't get more conclusions.
For the process of clipping screenshots, we can easily modify it according to the official example.
Capturing an Image
The APIs used are described in detail in the example.
You only need to change some of the code, such as changing the cropped area, and the example also shows how to save the cropped area as a file.
int CaptureAnImage(HWND hWnd)
{
HDC hdcScreen;
HDC hdcWindow;
HDC hdcMemDC = NULL;
HBITMAP hbmScreen = NULL;
BITMAP bmpScreen;
RECT rc;
rc.left = 0;
rc.top = 0;
rc.right = 800;
rc.bottom = 600;
// Retrieve the handle to a display device context for the client
// area of the window.
hdcScreen = GetDC(NULL);
hdcWindow = GetDC(hWnd);
// Create a compatible DC which is used in a BitBlt from the window DC
hdcMemDC = CreateCompatibleDC(hdcWindow);
if (!hdcMemDC)
{
MessageBox(hWnd, L"CreateCompatibleDC has failed", L"Failed", MB_OK);
}
// Get the client area for size calculation
RECT rcClient;
GetClientRect(hWnd, &rcClient);
//This is the best stretch mode
SetStretchBltMode(hdcWindow, HALFTONE);
//The source DC is the entire screen and the destination DC is the current window (HWND)
if (!StretchBlt(hdcWindow,
0, 0,
rcClient.right, rcClient.bottom,
hdcScreen,
0, 0,
GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN),
SRCCOPY))
{
MessageBox(hWnd, L"StretchBlt has failed", L"Failed", MB_OK);
}
// Create a compatible bitmap from the Window DC
hbmScreen = CreateCompatibleBitmap(hdcWindow, rc.right - rc.left, rc.bottom - rc.top);
if (!hbmScreen)
{
MessageBox(hWnd, L"CreateCompatibleBitmap Failed", L"Failed", MB_OK);
}
// Select the compatible bitmap into the compatible memory DC.
SelectObject(hdcMemDC, hbmScreen);
// Bit block transfer into our compatible memory DC.
if (!BitBlt(hdcMemDC,
rc.left, rc.top,
rc.right - rc.left, rc.bottom - rc.top,
hdcWindow,
0, 0,
SRCCOPY))
{
MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK);
}
// HBITMAP bmpnew = crop_image(rc, hbmScreen);
// 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(hdcWindow, 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(L"captureqwsx.bmp",
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
DeleteObject(hbmScreen);
DeleteObject(hdcMemDC);
ReleaseDC(NULL, hdcScreen);
ReleaseDC(hWnd, hdcWindow);
return 0;
}
It will get a cropped screenshot(800x600) file of the current screen.
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.
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);
}
I'm trying to get the bits of a window icon, in order to send them through a socket. To test if I'm hable to get the BITMAP corresponding to the HICON, I save it on a file.
The problem is that, opening the file where I've saved the bitmap, it appear monochrome (black and white, not grey scale).
Here is the BITMAPINFO struct:
info.biSize = sizeof(BITMAPINFOHEADER);
info.biWidth = bitmap.bmWidth;
info.biHeight = bitmap.bmHeight;
info.biPlanes = 1;
info.biBitCount = 32;
info.biCompression = BI_RGB;
info.biSizeImage = 0;
info.biXPelsPerMeter = 0;
info.biYPelsPerMeter = 0;
info.biClrUsed = 0;
info.biClrImportant = 0;
And I compute the size of the bitmap as:
DWORD dwBmpSize2 = ((bitmap.bmWidth * bi.biBitCount + 31) / 32) * 4 * bitmap.bmHeight;
Then:
// 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);
//Prendo i bit dell'icona
HDC hdc = GetDC(hwnd);
GetDIBits(hdc, hbit, 0, (UINT)bitmap.bmHeight, lpbitmap, (BITMAPINFO*)&bi,DIB_RGB_COLORS);
wcout « "lpbitmap buffer: "« lpbitmap « endl;
// A file is created, this is where we will save the screen capture.
HANDLE hFile = CreateFile(L"captureqwsx.bmp",
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);
This question already has answers here:
Save HBITMAP to *.bmp file using only Win32
(5 answers)
Closed 8 years ago.
Ok, whole story is, I am trying to use Leptonica+Tesseract OCR in C++ to take a screenshot, save it to a *.bmp file, then load it back up to OCR with it. I won't need to do this frequently, but as I cannot seem to copy the screenshot data directly into a Leptonica PIX structure, I need to save it to a file first..actually a solution to this would be preferably.
Here's some code I've found online, trying to help me out.
Screen cap:
HBITMAP ScreenCapture(){
int width=100;
int height=100;
// get the device context of the screen
HDC hScreenDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
// and a device context to put it in
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int x = GetDeviceCaps(hScreenDC, HORZRES);
int y = GetDeviceCaps(hScreenDC, VERTRES);
// maybe worth checking these are positive values
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, x, y);
// 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);
//GlobalAlloc(GPTR, hBitmap)
WriteDIB(L"test.bmp", (HGLOBAL)hBitmap);
// clean up
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);
return hBitmap;
// now your image is held in hBitmap. You can save it or do whatever with it
}
Attempt to write function:
BOOL WriteDIB( LPTSTR szFile, HANDLE hDIB)
{
cout<<endl<<"Running save function";
/*HANDLE hDIB=GlobalAlloc(GPTR, sizeof(hDIBtochange));//this doesn't work, the result is four. Also the HANDLE parameter's name would be changed to hDIBtochange, so that the rest of the function uses the old 'hDIB' throughout
cout<<endl<<sizeof(hDIBtochange);*/
BITMAPFILEHEADER hdr;
LPBITMAPINFOHEADER lpbi;
if (!hDIB)
return FALSE;
CFile file;
if( !file.Open( szFile, CFile::modeWrite|CFile::modeCreate) )
return FALSE;
lpbi = (LPBITMAPINFOHEADER)hDIB;
int nColors = 1 << lpbi->biBitCount;
// Fill in the fields of the file header
hdr.bfType = ((WORD) ('M' << 8) | 'B'); // is always "BM"
hdr.bfSize = GlobalSize (hDIB) + sizeof( hdr );
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
hdr.bfOffBits = (DWORD) (sizeof( hdr ) + lpbi->biSize + nColors * sizeof(RGBQUAD));
// Write the file header
file.Write( &hdr, sizeof(hdr) );
// Write the DIB header and the bits
file.Write( lpbi, GlobalSize(hDIB) );
return TRUE;
}
Shamelessly copied from people's posts over the years.
Ok! Problem I face is, I cannot seem to understand how to GlobalAlloc the Hbitmap into a globally accessible Handle, that can be converted or use with LPBITMAPINFOHEADER.
Soon as lpbi is created, every single field inside of it is "Unable to read memory" error in Visual Studio 2012 debugging. It's inaccessible, despite being created.
Solutions..
Go straight from screencap to PIX, inside of memory..
Find a way to save to bitmap and create them periodically to read..
Find another way entirely that makes more sense..
Preferring first, but, I'm asking for a solution in this, to the second one..or third.
If you need more info I can try to provide it. This mostly boils down to "I've never done code like this before and it wasn't taught in my classes so I'm trying to learn as I go".
A much easier way to save an HBITMAP to file is to make use of GDI+.
This gives you the advantage of being able to save to any format that windows supports natively, while freeing you from the muck of playing around with or even needing to understand, various image formats.
In the below example, I've just used LoadImage as a quik and dirty way of loading a pre-existing image - you could simply use the HBITMAP you've already captured.
Here's an example that loads a bitmap and saves it again. (I had initially used "image/png" as the output type, along with an appropriate output filename)
#include <windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if(size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == NULL)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT j = 0; j < num; ++j)
{
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HBITMAP hBitmap = (HBITMAP)LoadImage(GetModuleHandle(NULL), "babe.bmp", IMAGE_BITMAP, 0,0, LR_LOADFROMFILE);
Bitmap *image = new Bitmap(hBitmap, NULL);
CLSID myClsId;
int retVal = GetEncoderClsid(L"image/bmp", &myClsId);
image->Save(L"output.bmp", &myClsId, NULL);
delete image;
GdiplusShutdown(gdiplusToken);
return 0;
}
I recently had to do the same thing you are doing and successfully used GlobalAlloc.
The basis of this code is from This MSDN Article.
It looks like you Got your example code from here.
MSDN is really reliable for win32 operations, definitely prefer it over other sites in my experaince.
What seems to be happening is that the sizeof(hDIBtochange) is returning 4, so you are only allocating 4 bytes of memory. which would not be enough to hold a pbi structure.
Here is my code with a GlobalAlloc which hopefully will show the correct usage.
void
WriteBmpTofile(const bool remote, LPSTR pszFile, PBITMAPINFO pbi, HBITMAP hBmp, HDC hDC)
{
HANDLE hFile;
BITMAPFILEHEADER hdr;
PBITMAPINFOHEADER pbih;
LPBYTE lpBits;
DWORD dwTemp;
pbih = (PBITMAPINFOHEADER)pbi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if(!lpBits)
{
return; // could not allocate bitmap
}
GetDIBits(hDC, hBmp, 0, (WORD)pbih->biHeight, lpBits, pbi, DIB_RGB_COLORS);
hFile = CreateFile(pszFile,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
return; // Could not open screenshot file
}
// type == BM
hdr.bfType = 0x4d42;
hdr.bfSize = (sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
hdr.bfOffBits = sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);
// write the bitmap file header to file
WriteFile(hFile, (LPVOID)&hdr, sizeof(BITMAPFILEHEADER), &dwTemp, NULL);
// write the bitmap header to file
WriteFile(hFile, (LPVOID)pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof(RGBQUAD), &dwTemp, NULL);
// copy the bitmap colour data into the file
WriteFile(hFile, (LPSTR)lpBits, pbih->biSizeImage, &dwTemp, NULL);
CloseHandle(hFile);
GlobalFree((HGLOBAL)lpBits);
}
Here is the top function in that MSDN article, if you need it (again modified by me).
PBITMAPINFO
Print::CreateBitmapInfo(HBITMAP hBmp)
{
BITMAP bmp;
PBITMAPINFO pbmi;
GetObject(hBmp, sizeof(BITMAP), &bmp);
pbmi = static_cast<PBITMAPINFO>(LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER)));
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes; // we are assuming that there is only one plane
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
// no compression this is an rgb bitmap
pbmi->bmiHeader.biCompression = BI_RGB;
// calculate size and align to a DWORD (8bit), we are assuming there is only one plane.
pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * bmp.bmBitsPixel +31) & -31) * pbmi->bmiHeader.biHeight;
// all device colours are important
pbmi->bmiHeader.biClrImportant = 0;
return pbmi;
}
I'm guessing you got your code from here Storing an Image. A while back I had to modify the code to work with WinCE 5.0 and WinCE 6.0. Here is the beta-sample though it is kinda messy. It does it without the GlobalAlloc. It uses CreateDibSection instead.
int CreateBMPFile(HWND hwnd, LPCTSTR pszFile, PBITMAPINFO pbi,
HBITMAP hBMP, HDC hDC)
{
HANDLE hf; // file handle
BITMAPFILEHEADER hdr; // bitmap file-header
PBITMAPINFOHEADER pbih; // bitmap info-header
//LPBYTE lpBits; // memory pointer
DWORD dwTotal; // total count of bytes
DWORD cb; // incremental count of bytes
BYTE *hp; // byte pointer
DWORD dwTmp;
int ret = 0;
pbi = CreateBitmapInfoStruct(NULL, hBMP);
if(pbi == NULL)
{
return ret;
}
pbih = (PBITMAPINFOHEADER) pbi;
/*
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits)
{
//errhandler("GlobalAlloc", hwnd);
return;
}
*/
RGBQUAD *rgbq;
rgbq = pbi->bmiColors;
PALETTEENTRY pe[256];
GetSystemPaletteEntries(hDC, 0, pbih->biClrUsed, pe);
for(DWORD i = 0; i < pbih->biClrUsed; i++)
{
rgbq[i].rgbRed = pe[i].peRed;
rgbq[i].rgbBlue = pe[i].peBlue;
rgbq[i].rgbGreen = pe[i].peGreen;
rgbq[i].rgbReserved = 0;
}
// CE5.0 + CE6.0
HDC tHDC;
tHDC = CreateCompatibleDC(hDC);
HBITMAP h = CreateDIBSection(hDC, pbi, DIB_PAL_COLORS, (void **)&hp, NULL, 0);
if(h == NULL)
{
goto close_bmp;
}
SelectObject(tHDC, h);
BitBlt(tHDC, 0, 0, SCREEN_W, SCREEN_H, hDC, 0, 0, SRCCOPY);
/*
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
if (!GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,
DIB_RGB_COLORS))
{
//errhandler("GetDIBits", hwnd);
return;
}
*/
// Create the .BMP file.
hf = CreateFile(pszFile,
GENERIC_READ | GENERIC_WRITE,
(DWORD) 0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hf == INVALID_HANDLE_VALUE)
{
//errhandler("CreateFile", hwnd);
goto close_bmp;
}
hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof (RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),
(LPDWORD) &dwTmp, NULL))
{
//errhandler("WriteFile", hwnd);
goto close_bmp;
}
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof (RGBQUAD),
(LPDWORD) &dwTmp, ( NULL)))
{
//errhandler("WriteFile", hwnd);
}
// Copy the array of color indices into the .BMP file.
dwTotal = cb = pbih->biSizeImage;
//hp = lpBits;
if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL))
{
//errhandler("WriteFile", hwnd);
goto close_bmp;
}
close_bmp:
// Close the .BMP file.
if(hf != INVALID_HANDLE_VALUE)
{
if (!CloseHandle(hf))
{
//errhandler("CloseHandle", hwnd);
}
else
{
ret = 1;
}
}
// Free memory.
// GlobalFree((HGLOBAL)lpBits);
if(tHDC != NULL)
DeleteObject(tHDC);
if(h != NULL)
DeleteObject(h);
if(pbi != NULL)
{
//LocalFree(pbi);
free(pbi);
}
return ret;
}
I'm writing some code that will take a screenshot of another application, given its' window handle, in C++. The method I am using is to use BitBlt. My application is successfully taking the screenshot and I have a function that will save that image data out to a bmp file.
The screenshot contains the window's chrome though. That is, the border and the title bar. Based on my understanding, GetClientRect is supposed to exclude the window's border and title bar. I understand that GetWindowRect returns the coordinates within the user's desktop, and GetClientRect returns the coordinates relative to the application itself.
I notice in my screenshots that the title bar and left border are visible, but the right border and bottom of the application are cut off. So, I'm thinking that if I want to exclude the title and borders then I need to be doing some sort of combination of GetWindowRect and GetClientRect, and using information about the window itself to offset the GetClientRect dimensions by whatever the height of the window's title bar is, for example.
Does this sound accurate, or is my code below doing something wrong?
#include <Windows.h>
#include "ScreenshotManager.h"
namespace Managers {
ScreenshotManager::ScreenshotManager(HWND gameHandle) {
// get a device context for the window
m_gameContext = GetWindowDC(gameHandle);
// create a compatible device context for bitblt
m_bitmapContext = CreateCompatibleDC(m_gameContext);
// get window client area dimensions
GetClientRect(gameHandle, &m_gameClientArea);
}
bool ScreenshotManager::TakeScreenshot() {
// create a compatible bitmap for the game screenshots
m_bitmap = CreateCompatibleBitmap(m_gameContext, m_gameClientArea.right, m_gameClientArea.bottom);
// select the bitmap into the compatible device context
SelectObject(m_bitmapContext, m_bitmap);
// perform bit block transfer
if (BitBlt(m_bitmapContext, 0, 0, m_gameClientArea.right, m_gameClientArea.bottom, m_gameContext, 0, 0, SRCCOPY) == false)
return false;
// get information about the taken screenshot
GetObject(m_bitmap, sizeof(BITMAP), &m_bitmapInformation);
return true;
}
void ScreenshotManager::SaveScreenshot(LPCWSTR outputPath) {
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = m_bitmapInformation.bmWidth;
bi.biHeight = m_bitmapInformation.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 = ((m_bitmapInformation.bmWidth * bi.biBitCount + 31) / 32) * 4 * m_bitmapInformation.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(m_gameContext, m_bitmap, 0, (UINT)m_bitmapInformation.bmHeight, lpbitmap, (BITMAPINFO *)&bi, DIB_RGB_COLORS);
// A file is created, this is where we will save the screen capture.
HANDLE hFile = CreateFile(outputPath, 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);
}
}
GetClientRect() does not include the border and title bar. All it does is tell you the dimensions of the client area.
BitBlt() copies a rectangular region of pixels from one device context to another. In this example the source DC is a window DC, so the origin coordinates are relative to that window.
What your code is doing is copying a client-sized rectangle from the window’s origin. (That’s why the right and bottom edges go missing.)
You might be interested in AdjustWindowRectEx() to help identify the coordinates of the region you want to copy.