How to update a user created Bitmap in the Windows API - c++

In my code I quickly generate images on the fly, and I want to display them as quickly as possible. So the first time I create my image, I create a new BITMAP, but instead of deleting the old one and creating a new one for every subsequent image, I just want to copy my data back into the existing one. Here is my code to do both the initial creation and the updating. The creation works just fine, but the updating one doesn't work.
BITMAPINFO bi;
HBITMAP Frame::CreateBitmap(HWND hwnd, int tol1, int tol2, bool useWhite, bool useBackground)
{
ZeroMemory(&bi.bmiHeader, sizeof(BITMAPINFOHEADER));
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = width;
bi.bmiHeader.biHeight = height;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 24;
bi.bmiHeader.biCompression = BI_RGB;
ZeroMemory(bi.bmiColors, sizeof(RGBQUAD));
// Allocate memory for bitmap bits
int size = height * width;
Pixel* newPixels = new Pixel[size];
// Recompute the output
//memcpy(newPixels, pixels, size*3);
ComputeOutput(newPixels, tol1, tol2, useWhite, useBackground);
HBITMAP bitmap = CreateDIBitmap(GetDC(hwnd), &bi.bmiHeader, CBM_INIT, newPixels, &bi, DIB_RGB_COLORS);
delete newPixels;
return bitmap;
}
and
void Frame::UpdateBitmap(HWND hwnd, HBITMAP bitmap, int tol1, int tol2, bool useWhite, bool useBackground)
{
ZeroMemory(&bi.bmiHeader, sizeof(BITMAPINFOHEADER));
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
HDC hdc = GetDC(hwnd);
if(!GetDIBits(hdc, bitmap, 0, bi.bmiHeader.biHeight, NULL, &bi, DIB_RGB_COLORS))
MessageBox(NULL, "Can't get base image info!", "Error!", MB_ICONEXCLAMATION | MB_OK);
// Allocate memory for bitmap bits
int size = height * width;
Pixel* newPixels = new Pixel[size];
// Recompute the output
//memcpy(newPixels, pixels, size*3);
ComputeOutput(newPixels, tol1, tol2, useWhite, useBackground);
// Push back to windows
if(!SetDIBits(hdc, bitmap, 0, bi.bmiHeader.biHeight, newPixels, &bi, DIB_RGB_COLORS))
MessageBox(NULL, "Can't set pixel data!", "Error!", MB_ICONEXCLAMATION | MB_OK);
delete newPixels;
}
where the Pixel struct is just this:
struct Pixel { unsigned char b, g, r; };
Why does my update function not work. I always get the MessageBox for "Can't set pixel data!" I used code similar to this when I was loading in the original bitmap from file, then editing the data, but now when I manually create it, it doesn't work.

to write to a bitmap you have to Select it into a DeviceContext:
hdcMemDC = CreateCompatibleDC(hdc);
... // adjust hdcMem to right sizes (see MSDN : Capturing an Image)
SelectObject(hdcMemDC,bitmap);
... // your Bitmap drawing
BitBlt( hdcMemDC, ... ) // to copy to hdc.
ReleaseDC(hWnd, hdcMemDC);
Also CreateDIBitmap( GetDC(hwnd),... ) needs a ReleaseDC(hWnd ,??? );
You must assign GetDC(...) to a variable so you can release it.

Please, create your bitmap with CreateDIBSection.
This creates a HBITMAP to a bitmap and returns a pointer to a usermode accessible buffer. In the format you specify.

Related

Crop an HBITMAP with C++ on Windows

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.

How to turn a screenshot bitmap to a cv::Mat

I currently am trying to take a screenshot of the screen, and then get it into a format editable by OpenCV. The code I'm using is from the microsoft website, https://learn.microsoft.com/en-gb/windows/win32/gdi/capturing-an-image. The code uses the "Windows.h" library. The easiest way of doing it is obviously to just save the bitmap as a .bmp, then open it using opencv. However, I would like it to be more efficient than that, and I don't know how to. When I used the code, it outputted a char pointer, which I don't know how to convert to a cv::Mat. The code is below:
cv::Mat * Capture::GetMat()
{
cv::Mat * mat1;
MemoryHandle = NULL;
BitmapHandle = NULL;
// Find the handle for the device context of the entire screen, and the specific window specified.
ScreenHandle = GetDC(NULL);
WindowHandle = GetDC(hwnd);
//Make the compatible DC (Device Context) for storing the data in memory.
MemoryHandle = CreateCompatibleDC(WindowHandle);
//Make a compatible DC for the bitmap to be stored in.
BitmapHandle = CreateCompatibleBitmap(WindowHandle, width, height);
//Select the correct bitmap, and put it into memory using the memory handle.
SelectObject(MemoryHandle, BitmapHandle);
//Transfer the actual bitmap into the compatible memory DC.
BitBlt(MemoryHandle, 0, 0, 1920, 1080, WindowHandle, 0, 0, SRCCOPY);
//Get the bitmap from the handle, and ready it to be filed.
GetObject(BitmapHandle, sizeof(BITMAP), &Bitmap);
//Cofinguring INFO details.
bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfoHeader.biWidth = Bitmap.bmWidth;
bmpInfoHeader.biHeight = Bitmap.bmHeight;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = 32;
bmpInfoHeader.biCompression = BI_RGB;
bmpInfoHeader.biSizeImage = 0;
bmpInfoHeader.biXPelsPerMeter = 0;
bmpInfoHeader.biYPelsPerMeter = 0;
bmpInfoHeader.biClrUsed = 0;
bmpInfoHeader.biClrImportant = 0;
bmpSize = ((Bitmap.bmWidth * bmpInfoHeader.biBitCount + 31) / 32) * 4 * Bitmap.bmHeight;
memhnd = GlobalAlloc(GHND, bmpSize);
mat1 = (cv::Mat *)GlobalLock(memhnd);
std::cout << GetLastError() << std::endl;
return mat1;
}
int Capture::save_mat(cv::Mat * mat)
{
std::string FileName("P:/Photos/capture");
FileName += std::to_string(image_count_mat);
FileName += (const char*)(".jpg");
cv::Mat mat2 = *mat;
cv::imwrite(FileName.c_str(), mat2);
image_count_mat++;
return 0;
}
The class has these attributes:
private:
HWND hwnd;
HDC hdc;
int image_count_bitmap = 0;
int image_count_mat = 0;
int height;
int width;
HDC ScreenHandle;
HDC WindowHandle;
HDC MemoryHandle = NULL;
HBITMAP BitmapHandle = NULL;
BITMAP Bitmap;
BITMAPFILEHEADER bmpFileHeader;
BITMAPINFOHEADER bmpInfoHeader;
DWORD bmpSize;
HANDLE memhnd;
The GetMat() function works fine and doesn't output an error, although I have no idea how to check if the outputted cv::Mat is correct. When I run the save_mat() function however, the program crashes.
It is not recommended to store device context handles. For example a call to GetDC should be followed by ReleaseDC as soon as you are finished with the handle. You can store bitmap handles and memory dc, but in most cases it is not necessary.
Once you have copied the image in to bitmap, use GetDIBits to copy the bits in to cv::Mat as shown in the example below.
Note that your application needs DPI compatibility, for example with SetProcessDPIAware to find the correct desktop size.
This example uses 32-bit bitmap with CV_8UC4, but these GDI functions are 24-bit. You can also use 24-bit bitmap with CV_8UC3.
void screenshot()
{
auto w = GetSystemMetrics(SM_CXFULLSCREEN);
auto h = GetSystemMetrics(SM_CYFULLSCREEN);
auto hdc = GetDC(HWND_DESKTOP);
auto hbitmap = CreateCompatibleBitmap(hdc, w, h);
auto memdc = CreateCompatibleDC(hdc);
auto oldbmp = SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
cv::Mat mat(h, w, CV_8UC4);
BITMAPINFOHEADER bi = { sizeof(bi), w, -h, 1, 32, BI_RGB };
GetDIBits(hdc, hbitmap, 0, h, mat.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
cv::imwrite("screenshot.png", mat);
SelectObject(memdc, oldbmp);
DeleteDC(memdc);
DeleteObject(hbitmap);
ReleaseDC(HWND_DESKTOP, hdc);
}

C++ Windows function to save contents of window to bitmap

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);
}

How to construct a GDI+ Bitmap object from a Device-Dependent HBITMAP

I want to use GDI+ method Image::Save() to save a DDB to a file in the following scenario:
HBITMAP hBitmap = CreateCompatibleBitmap(hDC, 200, 200) ;
...
//hBitmap is a DDB so I need to pass an HPALETTE
Gdiplus::Bitmap(hBitmap, ???HPALETTE??? ).Save(L"file.png", ...) ;
The problem is that Bitmap constructor asks for an HPALETTE when the bitmap is not a device-independent bitmap.
Where do I get the necessary HPALETTE from?
FOLLOWUP:
One of the answers suggests passing NULL as the HPALETTE parameter.
Here is a working example that does so. The result is a purely black and white image where all colors are lost.
#include <windows.h>
#include <gdiplus.h>
int main(){
using namespace Gdiplus ;
GdiplusStartupInput gdiplusStartupInput ;
ULONG_PTR gdiplusToken ;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) ;
CLSID pngEncoder = {0x557cf406, 0x1a04, 0x11d3, {0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e} } ;
HDC dcHndl = CreateCompatibleDC(NULL) ;
HBITMAP hBitmap = CreateCompatibleBitmap(dcHndl, 200, 200) ;
SelectObject(dcHndl, hBitmap) ;
BitBlt(dcHndl, 0,0, 200,200, GetDC(NULL), 0,0, SRCCOPY|CAPTUREBLT) ;
Bitmap(hBitmap, NULL).Save(L"file.png", &pngEncoder) ;
}
First (and this is unrelated to your main question):
When creating a bitmap for screen shot, don't use a memory dc because that creates a monochrome bitmap. That's the main reason you are getting a black and white image (on my computer I just get a black image).
Don't use GetDC(0) inside another function. Every call to GetDC match have a matching ReleaseDC to avoid resource leak.
After calling BitBlt it is good practice to select hbitmap out of dc because you are basically finished drawing on dc.
The following code will work on Windows 10
int w = 800;
int h = 600;
HDC hdc = GetDC(HWND_DESKTOP);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, w, h);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, w, h, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
SelectObject(memdc, oldbmp);
Bitmap(hbitmap, NULL).Save(filename, &pngEncoder);
DeleteObject(hbitmap);
DeleteDC(memdc);
ReleaseDC(HWND_DESKTOP, hdc);
Back to your question regarding the documentation:
Type: HPALETTE
Handle to a GDI palette used to define the bitmap colors if hbm is not a device-independent bitmap (DIB).
In addition,
Do not pass to the Bitmap::FromHBITMAP method a GDI bitmap or a GDI palette that is currently (or was previously) selected into a device context.
The code I posted obeys only one rule, that GDI bitmap is not currently selected in to a device context (but it was previously selected).
The documentation may apply to older versions of Windows. As far as I can see MFC's CImage class does not follow all these rules. New computer displays are all 24 or 32 bit, I don't know how you would get a palette for it.
To follow the documentation to the letter, you can convert DDB to DIB section, using CreateDIBSection and GetDIBits. Use the new DIB section hbitmap_dib in Bitmap::FromHBITMAP. This will satisfy all of the conditions: hbitmap is dib, it is not (and was not) selected in to a device context.
Or, Gdiplus::Bitmap has another method Bitmap::FromBITMAPINFO. If there is no palette, you can use this code instead:
HDC hdc = GetDC(HWND_DESKTOP);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, w, h);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, 800, 600, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
SelectObject(memdc, oldbmp);
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
int size = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
BITMAPINFO info{ sizeof(info), bm.bmWidth, bm.bmHeight, 1, bm.bmBitsPixel, BI_RGB, size };
std::vector<char> bits(size);
GetDIBits(memdc, hbitmap, 0, bm.bmHeight, &bits[0], &info, DIB_RGB_COLORS);
Bitmap *bitmap = Bitmap::FromBITMAPINFO(&info, &bits[0]);
bitmap->Save(filename, &pngEncoder);
delete bitmap;
DeleteObject(hbitmap);
DeleteDC(memdc);
ReleaseDC(HWND_DESKTOP, hdc);
As CreateCompatibleBitmap remarks sate if you are dealing with color bitmaps we can also assume that hDC is a nonmemory device context (because memory device context will only create monochrome bitmaps) and the color palette used by this bitmap is the same color palette used by this device context. You can query it using GetCurrentObject method. However remarks to Bitmap.Bitmap(HBITMAP, HPALETTE) constructor state:
Do not pass to the GDI+ Bitmap::Bitmap constructor a GDI bitmap or a GDI palette that is currently (or was previously) selected into a device context.
So you can not used current device context palette directly and need to create a copy of it instead.
/// <returns>
/// Handle to palette currently selected into device context without granting ownership.
/// </returns>
_Check_return_ ::HPALETTE
Fetch_CurrentPalette(_In_ ::HDC const h_dc)
{
assert(h_dc);
::HGDIOBJ const h_palette_object{::GetCurrentObject(h_dc, OBJ_PAL)}; // not owned
assert(h_palette_object);
assert(OBJ_PAL == ::GetObjectType(h_palette_object));
// Perform unchecked conversion of generic GDI object descriptor to GDI palette descriptor.
::HPALETTE h_current_palette{}; // not owned
{
static_assert(sizeof(h_palette_object) == sizeof(h_current_palette), "wat");
::memcpy
(
::std::addressof(h_current_palette)
, ::std::addressof(h_palette_object)
, sizeof(h_current_palette)
);
}
return(h_current_palette);
}
/// <returns>
/// Handle to palette copy with granting ownership.
/// </returns>
_Check_return_ ::HPALETTE
Make_PaletteCopy(_In_ ::HPALETTE const h_palette)
{
assert(h_palette);
::UINT const first_entry_index{};
::UINT entries_count{};
::LPPALETTEENTRY p_entries{};
// Figure out how many entries palette contains.
entries_count = ::GetPaletteEntries(h_palette, first_entry_index, entries_count, p_entries);
assert(1 < entries_count);
assert(entries_count <= ::std::numeric_limits< decltype(LOGPALETTE::palNumEntries) >::max());
// This buffer will hold palette description which contains first PALETTEENTRY as last field.
// followed by the rest of PALETTEENTRY items.
::std::unique_ptr< ::std::uint8_t[] > const p_buffer
{
new ::std::uint8_t[sizeof(::LOGPALETTE) + (sizeof(::PALETTEENTRY) * (entries_count - 1u))]
};
// Perform unchecked conversion of buffer pointer to palette description pointer.
::LOGPALETTE * p_description{};
{
::std::uint8_t * const p_buffer_bytes{p_buffer.get()};
static_assert(sizeof(p_buffer_bytes) == sizeof(p_description), "wat");
::memcpy
(
::std::addressof(p_description)
, ::std::addressof(p_buffer_bytes)
, sizeof(p_description)
);
}
// Copy palette entries into buffer.
p_entries = static_cast< ::LPPALETTEENTRY >(p_description->palPalEntry);
::UINT const copied_entries_count
{
::GetPaletteEntries(h_palette, first_entry_index, entries_count, p_entries)
};
assert(copied_entries_count == entries_count);
// Create palette copy.
p_description->palVersion = 0x300; // magic
p_description->palNumEntries = static_cast< ::WORD >(copied_entries_count);
::HPALETTE const h_copied_palette{::CreatePalette(p_description)}; // owned
assert(h_copied_palette);
return(h_copied_palette);
}
::HPALETTE const hPal{Make_PaletteCopy(Fetch_CurrentPalette(hDC))}; // owned
assert(hPal);
::HBITMAP const hBitmap{::CreateCompatibleBitmap(hDC, 200, 200)}; // owned
assert(hBitmap);
{
::Gdiplus::Bitmap bmp{hBitmap, hPal};
assert(::Gdiplus::Status::Ok == bmp.GetLastStatus());
// Do something...
}
// Delete palette and bitmap after GDI+ bitmap object went out of scope.
if(FALSE == ::DeleteObject(hPal))
{
assert(false);
}
if(FALSE == ::DeleteObject(hBitmap))
{
assert(false);
}
You can pass NULL. Sample code below.
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
GUID encoder = {};
GetGdiplusEncoderClsid(L"image/png", &encoder); // https://stackoverflow.com/a/5346026/104458
HDC hdc = GetDC(NULL);
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, 200, 200);
Bitmap bmp(hBitmap, NULL);
bmp.Save(L"File.png", &encoder);
return 0;
}

Screenshot code works but goes on taking RAM and crashes

I'm trying to make a program which makes screenshot of the entire desktop or a specific window, and draw it in a SFML window. Initially it goes fine but after 10 seconds it stop capturing. I also noticed that it go on consuming RAM even though we talk about only 20kb per second. I tried to join code found around the web. When it stops working it prints "2" and then infinite "1", from the code you quickly realize where these errors are.
#include <SFML/Graphics.hpp>
#include <Windows.h>
HBITMAP ScreenShot(HWND hParent, int x, int y, int nWidth, int nHeight)
{
//Get a DC from the parent window
HDC hDC = GetDC(hParent);
//Create a memory DC to store the picture to
HDC hMemDC = CreateCompatibleDC(hDC);
//Create the actual picture
HBITMAP hBackground = CreateCompatibleBitmap(hDC, nWidth, nHeight);
//Select the object and store what we got back
HBITMAP hOld = (HBITMAP)SelectObject(hMemDC, hBackground);
//Now do the actually painting into the MemDC (result will be in the selected object)
//Note: We ask to return on 0,0,Width,Height and take a blit from x,y
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, x, y, SRCCOPY);
//Restore the old bitmap (if any)
SelectObject(hMemDC, hOld);
//Release the DCs we created
ReleaseDC(hParent, hMemDC);
ReleaseDC(hParent, hDC);
//Return the picture (not a clean method, but you get the drill)
return hBackground;
}
bool SFMLLoadHBitmapAsImage(HBITMAP hBitmap, sf::Image *pPicture)
{
HWND hParent = FindWindow(NULL, TEXT("Calculator"));
//if (hParent == NULL)printf("%s", "w");
//Create a DC to get hBitmap information
HDC hDC = GetDC(hParent);
//Create BITMAPINFO variable, set size
BITMAPINFO MyBMInfo = { 0 };
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
//Get the BITMAPINFO structure from the bitmap
if (0 == GetDIBits(hDC, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS))
{
printf("%s", "1");
return false;
}
//Create the bitmap pixel array each element is [b,g,r]
BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];
//Setting up the structure of the buffer to be received
MyBMInfo.bmiHeader.biCompression = BI_RGB; // No-compression
//Now get the actual data from the picture
if (0 == GetDIBits(hDC, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS))
{
printf("%s", "2");
return false;
}
//Now create an array of SFML pixels we want to fill
sf::Uint8 *lpPixelWithAlpha = new sf::Uint8[MyBMInfo.bmiHeader.biSizeImage +
(MyBMInfo.bmiHeader.biSizeImage / 3) / 3]; //Add room for alpha
//Loop through each pixel, with steps of four RGBA!
for (int x = 0; x < MyBMInfo.bmiHeader.biSizeImage; x += 4)
{
lpPixelWithAlpha[x] = lpPixels[x + 2]; //lpPixels = r
lpPixelWithAlpha[x + 1] = lpPixels[x + 1]; //lpPixels = g
lpPixelWithAlpha[x + 2] = lpPixels[x]; //lpPixels = b
lpPixelWithAlpha[x + 3] = 255; //Nada alpha (just to adjust if you like)
}
//Remove old DIBsection
delete[] lpPixels;
//Load picture, now with correct pixels and alpha channel
pPicture->create(MyBMInfo.bmiHeader.biWidth,
MyBMInfo.bmiHeader.biHeight, lpPixelWithAlpha);
//Remove the pixels with alphachannel
delete[] lpPixelWithAlpha;
//Release the DC
ReleaseDC(hParent, hDC);//::GDW()
//Notify ok!
return true;
}
int main()
{
.....
hBitmap = ScreenShot(FindWindow(NULL, TEXT("Calculator")), 0, 0, 640, 400);
SFMLLoadHBitmapAsImage(hBitmap, &picture);
.....
}