How do I get the byte representation of a BITMAPFILEHEADER and BITMAPINFOHEADER? - c++

I'm trying to take a screenshot of my computer screen using code, and then store the BYTEs of that image into an array rather than save the image right away using the following function:
void WINAPI CaptureScreenIntoByteArray(
BYTE*& screen_bytes,
DWORD& screen_bytes_size)
{
BITMAPFILEHEADER bfHeader;
BITMAPINFOHEADER biHeader;
BITMAPINFO bInfo;
HGDIOBJ hTempBitmap;
HBITMAP hBitmap;
BITMAP bAllDesktops;
HDC hDC, hMemDC;
LONG lWidth, lHeight;
BYTE* sb = NULL;
ZeroMemory(&bfHeader, sizeof(BITMAPFILEHEADER));
ZeroMemory(&biHeader, sizeof(BITMAPFILEHEADER));
ZeroMemory(&bInfo, sizeof(BITMAPINFO));
ZeroMemory(&bAllDesktops, sizeof(BITMAP));
hDC = GetDC(NULL);
hTempBitmap = GetCurrentObject(hDC, OBJ_BITMAP);
GetObjectW(hTempBitmap, sizeof(BITMAP), &bAllDesktops);
lWidth = bAllDesktops.bmWidth;
lHeight = bAllDesktops.bmHeight;
DeleteObject(hTempBitmap);
bfHeader.bfType = (WORD)('B' | ('M' << 8));
bfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
biHeader.biSize = sizeof(BITMAPINFOHEADER);
biHeader.biBitCount = 24;
biHeader.biCompression = BI_RGB;
biHeader.biPlanes = 1;
biHeader.biWidth = lWidth;
biHeader.biHeight = lHeight;
bInfo.bmiHeader = biHeader;
screen_bytes_size = (((24 * lWidth + 31) & ~31) / 8) * lHeight;
hMemDC = CreateCompatibleDC(hDC);
hBitmap = CreateDIBSection(hDC, &bInfo, DIB_RGB_COLORS, (VOID**)&sb, NULL, 0);
SelectObject(hMemDC, hBitmap);
int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY);
// Need to also copy bfHeader & biHeader bytes somehow...
screen_bytes = new BYTE[screen_bytes_size];
std::copy(sb, sb + screen_bytes_size, screen_bytes);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DeleteObject(hBitmap);
}
My specific issue is that I need to have the bfHeader and biHeader saved into the byte array also (so that later on I can save the image as a file)... So this is the problem area...
// Need to also copy bfHeader & biHeader bytes somehow...
screen_bytes = new BYTE[screen_bytes_size];
std::copy(sb, sb + screen_bytes_size, screen_bytes);
How can I also copy the bfHeader and biHeader bytes into the array?
If needed, this is how you call the function...
BYTE* screen_bytes = NULL;
DWORD screen_bytes_size = 0;
CaptureScreenIntoByteArray(screen_bytes, screen_bytes_size);

You need to make the array big enough for the two structures as well.
screen_bytes = new BYTE[sizeof(BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER) + screen_bytes_size];
Then you can copy the two structures to the front of the array:
std::copy(&bfHeader, &bfHeader + 1, (BITMAPFILEHEADER*)screen_bytes);
std::copy(&biHeader, &biHeader + 1, (BITMAPINFOHEADER*)(screen_bytes + sizeof(BITMAPFILEHEADER)));
And copy the image data after the two structures.
std::copy(sb, sb + screen_bytes_size, screen_bytes + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));

Related

GetDIBits() sets destination pointer to NULL with no error

I am trying to put an application icon into a char array. The code below converts a HICON into a BITMAP, then attempts to extract the bytes from the BITMAP into a char array. As I step through the code, I observed that the second GetDIBits() modifies the destination pointer to NULL despite claiming 16 bytes were written. This behavior is very puzzling. I suspect that casting BITMAPINFOHEADER* into BITMAPINFO* might be problematic, but using a BITMAPINFO directly causes stack corruption upon exiting the function. Does anyone know why GetDIBits() behaves in such a way?
std::unique_ptr<char> getRawImg(HICON& icon)
{
// step 1 : get a bitmap from an application icon
ICONINFO iconInfo;
ZeroMemory(&iconInfo, sizeof(iconInfo));
BITMAP bitMap;
ZeroMemory(&bitMap, sizeof(bitMap));
HRESULT bRes = GetIconInfo(icon, &iconInfo);
int width;
int height;
int bitsPerPixel;
if (iconInfo.hbmColor) // color icon
{
if (GetObject(iconInfo.hbmColor, sizeof(bitMap), &bitMap))
{
width = bitMap.bmWidth;
height = bitMap.bmHeight;
bitsPerPixel = bitMap.bmBitsPixel;
}
}
else if (iconInfo.hbmMask) // black and white icon
{
if (GetObject(iconInfo.hbmMask, sizeof(bitMap), &bitMap))
{
width = bitMap.bmWidth;
height = bitMap.bmHeight / 2;
bitsPerPixel = 1;
}
}
// step 2 : extract bytes from the bitmap into a byte array
HBITMAP hBitmap = CreateBitmapIndirect(&bitMap);
int stride = (width * bitsPerPixel + 31) / 32 * 4;
HDC hdc = GetDC(NULL);
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width;
bi.biHeight = height;
bi.biPlanes = 1;
bi.biBitCount = bitsPerPixel;
bi.biCompression = BI_RGB;
bi.biSizeImage = stride * height;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
BITMAPINFO* pBitmapInfo = (BITMAPINFO*)&bi;
if (!GetDIBits(hdc, hBitmap, 0, 0, NULL, pBitmapInfo, DIB_RGB_COLORS)) {
// error
std::cout << "failed to get bitmap info" << std::endl;
}
std::unique_ptr<char> buffer(new char[bi.biWidth * bi.biHeight * bi.biBitCount / 8]);
// Buffer points to some address before calling GetDIBits(). After calling GetDIBits(), buffer points to NULL. bytesWritten is 16
int bytesWritten = GetDIBits(hdc, hBitmap, 0, height, (LPVOID)buffer.get(), pBitmapInfo, DIB_RGB_COLORS);
if (bytesWritten <= 0) {
// error
std::cout << "failed" << std::endl;
}
DeleteObject(hBitmap);
ReleaseDC(NULL, hdc);
if (iconInfo.hbmColor)
DeleteObject(iconInfo.hbmColor);
if (iconInfo.hbmMask)
DeleteObject(iconInfo.hbmMask);
return buffer;
}
You need to allocate a suitably-sized BITMAPINFO and cast it to BITMAPINFOHEADER* (or just use its bmiHeader member). Not allocate a BITMAPINFOHEADER and cast it to BITMAPINFO*. A BITMAPINFO consists of a BITMAPINFOHEADER followed by an array of 0 or more RGBQUAD elements for a color table. A BITMAPINFOHEADER itself does not contain the color table, but it does describe the color table that follows it.
Per the GetDIBits() documentation:
If the requested format for the DIB matches its internal format, the RGB values for the bitmap are copied. If the requested format doesn't match the internal format, a color table is synthesized...
If the lpvBits parameter is a valid pointer, the first six members of the BITMAPINFOHEADER structure must be initialized to specify the size and format of the DIB. The scan lines must be aligned on a DWORD except for RLE compressed bitmaps.
A bottom-up DIB is specified by setting the height to a positive number, while a top-down DIB is specified by setting the height to a negative number. The bitmap color table will be appended to the BITMAPINFO structure.
If lpvBits is NULL, GetDIBits examines the first member of the first structure pointed to by lpbi. This member must specify the size, in bytes, of a BITMAPCOREHEADER or a BITMAPINFOHEADER structure. The function uses the specified size to determine how the remaining members should be initialized.
If lpvBits is NULL and the bit count member of BITMAPINFO is initialized to zero, GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table. This technique can be used to query bitmap attributes.
So, in your case, you are setting lpvBits to NULL, but the BITMAPINFOHEADER::biBitCount field is not 0, so GetDIBits() will try to fill in the color table of the provided BITMAPINFO, but you are not allocating any memory to receive that color table. So GetDIBits() ends up corrupting the memory that follows the BITMAPINFOHEADER.
I think you did not guarantee that the hbm parameter of GetDIBits is compatible bitmap during the process of converting HICON to HBITMAP.
Try to use the following code and test :
#include <iostream>
#include <windows.h>
using namespace std;
HBITMAP getBmp(HICON hIcon)
{
HDC hDC = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hDC);
HBITMAP hMemBmp = CreateCompatibleBitmap(hDC, 32, 32);
HBITMAP hResultBmp = NULL;
HGDIOBJ hOrgBMP = SelectObject(hMemDC, hMemBmp);
DrawIconEx(hMemDC, 0, 0, hIcon, 32, 32, 0, NULL, DI_NORMAL);
hResultBmp = hMemBmp;
hMemBmp = NULL;
SelectObject(hMemDC, hOrgBMP);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DestroyIcon(hIcon);
return hResultBmp;
}
BYTE* getPixArray(HBITMAP hBitmap)
{
HDC hdc, hdcMem;
hdc = GetDC(NULL);
hdcMem = CreateCompatibleDC(hdc);
BITMAPINFO MyBMInfo = { 0 };
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
if (0 == GetDIBits(hdcMem, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS))
{
cout << " fail " << endl;
}
BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
MyBMInfo.bmiHeader.biBitCount = 32;
MyBMInfo.bmiHeader.biCompression = BI_RGB;
MyBMInfo.bmiHeader.biHeight = (MyBMInfo.bmiHeader.biHeight < 0) ? (-MyBMInfo.bmiHeader.biHeight) : (MyBMInfo.bmiHeader.biHeight);
// get the actual bitmap buffer
if (0 == GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS))
{
cout << " fail " << endl;
}
return lpPixels;
}
int main(int argc, const char* argv[])
{
HICON hIcon = (HICON)LoadImage(0, L"test.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
HBITMAP hbmp = getBmp(hIcon);
getPixArray(hbmp);
return 0;
}

C++ Memory Leak -- Need peer review [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I have a pretty big leak somewhere in this screencapture code I have.
My C++ is pretty bad so if anyone can point out the issues that would be fantastic!
Here's the offending code:
FString AWindow::CaptureWindow(HWND hwnd) {
HDC hdcSrc = GetWindowDC(hwnd);
RECT rawRect;
LPRECT rect = &rawRect;
GetWindowRect(hwnd, rect);
int width = rect->right - rect->left;
int height = rect->bottom - rect->top;
HDC hdcDest = CreateCompatibleDC(hdcSrc);
HBITMAP hBitmap = CreateCompatibleBitmap(hdcSrc, width, height);
HGDIOBJ h0ld = SelectObject(hdcDest, hBitmap);
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, SRCCOPY);
SelectObject(hdcDest, h0ld);
DeleteDC(hdcDest);
char* pImage = NULL;
pImage = (char*)GlobalLock(hBitmap);
BITMAP bmp;
PBITMAPINFO pbmi;
WORD cClrBits;
GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bmp);
//Convert the color format to a count of bits
cClrBits = (WORD)(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;
//Allocate memory for the BITMAPINFO structure.
if (cClrBits < 24) {
pbmi = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * (1i64 << cClrBits));
}
else {
pbmi = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER));
}
//Initialize the field in the BITMAPINFO structure
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biClrUsed = (1i64 << cClrBits);
pbmi->bmiHeader.biCompression = BI_RGB;
//Computer the number of bytes in the array of color
//indices and store the result in biSizeImage.
pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits + 31) & ~31) / 8
* pbmi->bmiHeader.biHeight;
//Set biClrImportant to 0, indicating that all of the device
//colors are important
pbmi->bmiHeader.biClrImportant = 0;
FString file = FPaths::Combine(*FPaths::GameDir(), TEXT("ScreenGrab/"), TEXT("Desktop.bmp"));
std::string mystring(TCHAR_TO_UTF8(*file));
std::wstring lpstring = std::wstring(mystring.begin(), mystring.end());
LPCWSTR realfile = lpstring.c_str();
FString error = CreateBMPFile(hwnd, realfile, pbmi, hBitmap, hdcSrc);
ReleaseDC(hwnd, hdcSrc);
DeleteObject(hBitmap);
return error;
}
I'm guessing there's some objects in there I'm forgetting to delete though I have no idea which ones they are.
Posting it here while I comment code out lines at a time to see if I can pinpoint the offending things.
Thanks
-Paul
::EDIT::
Upon further inspection I narrowed the severe leak down to the function saving the BMP.
Here that is:
FString AWindow::CreateBMPFile(HWND hwnd, LPCWSTR 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;
pbih = (PBITMAPINFOHEADER)pbi;
lpBits = (LPBYTE)GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits) {
return FString("global alloc failed");
}
// 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))
{
return FString("get di bits failed");
}
// 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)
{
return FString("invalid file handle");
}
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))
{
return FString("Write header failed");
}
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
if (!WriteFile(hf, (LPVOID)pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof(RGBQUAD),
(LPDWORD)&dwTmp, (NULL))) {
return FString("write info header failed");
}
// 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)) {
return FString("copy color failed");
}
// Close the .BMP file.
if (!CloseHandle(hf))
// Free memory.
GlobalFree((HGLOBAL)lpBits);
return FString("Completed ok?");
}
Thanks again for any help! Digital brownies for anyone who solves this mess!
The "pbmi" was locally allocated.
You forgot to use "LocalFree" to release the allocation.
Maybe there are more, but that was the first one I spotted.
Using RAII (resource acquistion is initialization) pattern, you don't have to sprinkle closes and frees before each return. Furthermore, you will prevent leaks if an exception is thrown (e.g. bad character encoding in UTF8 translation). Here is the idiom that #JesperJuhl is referring to in context of your code:
using file_raii_t = std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::CloseHandle)>;
using gmem_raii_t = std::unique_ptr<std::remove_pointer<HGLOBAL>::type, decltype(&::GlobalFree)>;
gmem_raii_t gmem(::GlobalAlloc(GMEM_FIXED, size), ::GlobalFree);
// gmem can be used like (!GetDIBits(..., gmem.get(), ...);
file_raii_t fh(::CreateFile(pszFile, GENERIC_READ | GENERIC_WRITE, (DWORD)0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL), ::CloseHandle);
//fh can be used like FileRead(fh.get()...);
This can be extended to include other APIs (even APIs that return resources as out parameters). In you case: GetWindowDC, LocalAlloc. Using RAII you can write fewer lines of exception-safe code.
Note: code from my aging memory. not tested in your code.

Black image when creating a Bitmap from a selection of the screen in C++

I am trying to get a screen capture of a selected region of the desktop.
The problem I am having is that the output (.bmp file) is just black.
I took CreateBitmapInfoStruct(); and CreateBMPFile(); from MSDN. The rest has been snips from the internet Frankenstein-ed together by me.
Here is what my code looks like presently:
#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
PBITMAPINFO CreateBitmapInfoStruct(HBITMAP hBmp)
{
BITMAP bmp;
PBITMAPINFO pbmi;
WORD cClrBits;
// Retrieve the bitmap color format, width, and height.
if (!GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp))
cout << "ERROR:1";
// Convert the color format to a count of bits.
cClrBits = (WORD)(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;
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits < 24)
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * (1<< cClrBits));
// There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel
else
pbmi = (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.
// The width must be DWORD aligned unless the bitmap is RLE
// compressed.
pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8
* pbmi->bmiHeader.biHeight;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
pbmi->bmiHeader.biClrImportant = 0;
return pbmi;
}
void CreateBMPFile(LPTSTR 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;
pbih = (PBITMAPINFOHEADER) pbi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits)
cout << "ERROR:2";
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,
DIB_RGB_COLORS);
// 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)
cout << "ERROR:4";
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))
{
cout << "ERROR:5";
}
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof (RGBQUAD),
(LPDWORD) &dwTmp, ( NULL)))
cout << "ERROR:6";
// 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))
cout << "ERROR:7";
// Close the .BMP file.
if (!CloseHandle(hf))
cout << "ERROR:8";
// Free memory.
GlobalFree((HGLOBAL)lpBits);
}
void getBit(string name) {
int nX = 300;
int nX2 = 600;
int nY = 300;
int nY2 = 700;
HDC hScrDC = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hScrDC);
int nWidth = nX2 - nX;
int nHeight = nY2 - nY;
HBITMAP hBitmap = CreateCompatibleBitmap(hScrDC, nWidth, nHeight);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, nWidth, nHeight,
hScrDC, nX, nY, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemDC, hOldBitmap);
DeleteDC(hScrDC);
DeleteDC(hMemDC);
// now your image is held in hBitmap. You can save it or do whatever with it
PBITMAPINFO pbi = CreateBitmapInfoStruct(hBitmap);
//File name
LPTSTR path = L"test.bmp";
CreateBMPFile(path,pbi,hBitmap,hMemDC);
}
void main(void) {
getBit("SAVE");
cout << "Done";
int wait;
cin >> wait;
}
The biggest problem is that GetDIBits fails because you're passing a handle to a deleted memory context instead of a handle to a valid device context. If you fix that, it works.
When debugging, it's always a good idea to check all of the return values.
Other things I've noticed:
You should not delete hSrcDC, since you didn't create it. Use ReleaseDC, which is the companion to GetDC.
Consider using CAPTUREBLT in your BitBlt call.

Compare two bitmap (device context - file)

I need to compare two bitmaps. One bitmap is loaded from a file, the second is a bitmap from a device context. The file bitmap is generated by the same program for test-purpose.
I am programming on vc10 / win7
I deliberately not handle error to keep clear the code on this post.
First step, I make a rgb24 bitmap file and save it as "test.bmp" :
void GetBitmap24FromDcToFile(HDC winDC, int x, int y, int w, int h)
{
int imgsize;
if((3 * w) % 4 > 0)
imgsize = ((3 * w) / 4 + 1) * 4 * h;
else if((3 * w) % 4 == 0)
imgsize = 3 * w * h;
BITMAPINFO bi;
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = w;
bi.bmiHeader.biHeight = h;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 24;
bi.bmiHeader.biCompression = BI_RGB;
bi.bmiHeader.biSizeImage = imgsize;
bi.bmiHeader.biXPelsPerMeter = 0;
bi.bmiHeader.biYPelsPerMeter = 0;
bi.bmiHeader.biClrUsed = 0;
bi.bmiHeader.biClrImportant = 0;
void *pvBits = NULL;
HBITMAP hbmp = ::CreateDIBSection(winDC, &bi, DIB_RGB_COLORS, &pvBits, NULL, 0);
HDC hdc = ::CreateCompatibleDC(winDC);
HBITMAP holdbmp = (HBITMAP)::SelectObject(hdc, hbmp);
::BitBlt(hdc, 0, 0, w, h, winDC, x, y, SRCCOPY);
HANDLE hFile = ::CreateFile(_T("test.bmp"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dwCnt;
BITMAPFILEHEADER bmfh;
ZeroMemory(&bmfh, sizeof(BITMAPFILEHEADER));
bmfh.bfType = 0x4d42;
bmfh.bfSize = imgsize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
WriteFile(hFile, (char*)&bmfh, sizeof(BITMAPFILEHEADER), &dwCnt, NULL);
WriteFile(hFile, (char*)&bi.bmiHeader, sizeof(BITMAPINFOHEADER), &dwCnt, NULL);
WriteFile(hFile, (char*)pvBits, imgsize, &dwCnt, NULL);
CloseHandle(hFile);
::SelectObject(hdc, holdbmp);
::DeleteDC(hdc);
::DeleteObject(hbmp);
}
Second step, i make a bitmap from a device context :
HBITMAP GetBitmap24FromDC(HDC winDC, int x, int y, int w, int h)
{
HDC hMemDC = ::CreateCompatibleDC( winDC );
HBITMAP hbmp; // = ::CreateCompatibleBitmap( winDC, w, h);
BITMAPINFOHEADER infoHeader;
infoHeader.biSize = sizeof(infoHeader);
infoHeader.biWidth = (LONG)w;
infoHeader.biHeight = (LONG)h;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = BI_RGB;
infoHeader.biSizeImage = 0;
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;
BITMAPINFO info;
info.bmiHeader = infoHeader;
unsigned char *mem;
hbmp = CreateDIBSection(winDC, &info, DIB_RGB_COLORS, (void**)&mem, 0, 0);
HBITMAP holdbmp = (HBITMAP) ::SelectObject(hMemDC, hbmp);
::BitBlt(hMemDC, 0, 0, w, h, winDC, x, y, SRCCOPY);
::SelectObject(hMemDC, holdbmp);
::DeleteDC(hMemDC);
return hbmp;
}
And i use this method for comparaison :
// Author: PJ Arends - codeproject
bool CompareBitmaps(HBITMAP HBitmapLeft, HBITMAP HBitmapRight)
{
if (HBitmapLeft == HBitmapRight)
{
return true;
}
if (NULL == HBitmapLeft || NULL == HBitmapRight)
{
return false;
}
bool bSame = false;
HDC hdc = GetDC(NULL);
BITMAPINFO BitmapInfoLeft = {0};
BITMAPINFO BitmapInfoRight = {0};
BitmapInfoLeft.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BitmapInfoRight.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
if (0 != GetDIBits(hdc, HBitmapLeft, 0, 0, NULL, &BitmapInfoLeft, DIB_RGB_COLORS) &&
0 != GetDIBits(hdc, HBitmapRight, 0, 0, NULL, &BitmapInfoRight, DIB_RGB_COLORS))
{
// Compare the BITMAPINFOHEADERs of the two bitmaps
if (0 == memcmp(&BitmapInfoLeft.bmiHeader, &BitmapInfoRight.bmiHeader,
sizeof(BITMAPINFOHEADER)))
{
// The BITMAPINFOHEADERs are the same so now compare the actual bitmap bits
BYTE *pLeftBits = (BYTE*)malloc(sizeof(BYTE) * BitmapInfoLeft.bmiHeader.biSizeImage);
BYTE *pRightBits = (BYTE*)malloc(sizeof(BYTE) * BitmapInfoRight.bmiHeader.biSizeImage);
BYTE *pByteLeft = NULL;
BYTE *pByteRight = NULL;
PBITMAPINFO pBitmapInfoLeft = &BitmapInfoLeft;
PBITMAPINFO pBitmapInfoRight = &BitmapInfoRight;
// calculate the size in BYTEs of the additional
// memory needed for the bmiColor table
int AdditionalMemory = 0;
switch (BitmapInfoLeft.bmiHeader.biBitCount)
{
case 1:
AdditionalMemory = 1 * sizeof(RGBQUAD);
break;
case 4:
AdditionalMemory = 15 * sizeof(RGBQUAD);
break;
case 8:
AdditionalMemory = 255 * sizeof(RGBQUAD);
break;
case 16:
case 32:
AdditionalMemory = 2 * sizeof(RGBQUAD);
}
if (AdditionalMemory)
{
// we have to allocate room for the bmiColor table that will be
// attached to our BITMAPINFO variables
pByteLeft = new BYTE[sizeof(BITMAPINFO) + AdditionalMemory];
if (pByteLeft)
{
memset(pByteLeft, 0, sizeof(BITMAPINFO) + AdditionalMemory);
memcpy(pByteLeft, pBitmapInfoLeft, sizeof(BITMAPINFO));
pBitmapInfoLeft = (PBITMAPINFO)pByteLeft;
}
pByteRight = new BYTE[sizeof(BITMAPINFO) + AdditionalMemory];
if (pByteRight)
{
memset(pByteRight, 0, sizeof(BITMAPINFO) + AdditionalMemory);
memcpy(pByteRight, pBitmapInfoRight, sizeof(BITMAPINFO));
pBitmapInfoRight = (PBITMAPINFO)pByteRight;
}
}
if (pLeftBits && pRightBits && pBitmapInfoLeft && pBitmapInfoRight)
{
// zero out the bitmap bit buffers
memset(pLeftBits, 0, BitmapInfoLeft.bmiHeader.biSizeImage);
memset(pRightBits, 0, BitmapInfoRight.bmiHeader.biSizeImage);
// fill the bit buffers with the actual bitmap bits
if (0 != GetDIBits(hdc, HBitmapLeft, 0,
pBitmapInfoLeft->bmiHeader.biHeight, pLeftBits, pBitmapInfoLeft,
DIB_RGB_COLORS) && 0 != GetDIBits(hdc, HBitmapRight, 0,
pBitmapInfoRight->bmiHeader.biHeight, pRightBits, pBitmapInfoRight,
DIB_RGB_COLORS))
{
// compare the actual bitmap bits of the two bitmaps
bSame = 0 == memcmp(pLeftBits, pRightBits,
pBitmapInfoLeft->bmiHeader.biSizeImage);
}
}
// clean up
free(pLeftBits);
free(pRightBits);
free(pByteLeft);
free(pByteRight);
}
}
ReleaseDC(NULL, hdc);
return bSame;
}
So, in my main code i have something like that :
(...)
HWND capture = ::FindWindow(_T("the_window_class"), NULL);
HDC winDC = ::GetDC(capture);
GetBitmap24FromDcToFile(winDC, 0, 0, 200, 200); // generate bitmap file "test.bmp"
HBITMAP bmpFile = (HBITMAP)LoadImage( NULL, _T("test.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION );
HBITMAP bmpMem = GetBitmap24FromDC(winDC, 0, 0, 200, 200); // get bitmap from DC
bool isEqual = CompareBitmaps(bmpFile, bmpMem); // test both bitmaps
if(isEqual)
AfxMessageBox(_T("Success"));
(...)
Comparaison between two files return true; two bitmaps from dc return true;
Comparaison between a bitmap file and a dc bitmap always return false.
After debugging, it passe the first test-condition (in the Compare method) where we check the BITMAPINFOHEADERs. It fail on the last memcmp() where we compare the bits of the two bitmaps.
In the debugger, the structure are the same for both bitmaps, I have only a small difference between the two pBitmapInfoLeft\pBitmapInfoRight->bmiColors field.
Checking the bits from the two bitmaps headers are the same (pLeftBits\pRightBits).
An idea, an alternative, an example? let me know! thank you!
JE
There's a sort of a bug though.
You use the BITMAPINFO structure, which is actually a fake, not designed to be used as-is.
The actual bitmap header consists of a fixed BITMAPINFOHEADER structure, and a variable-sized array of RGBQUAD structures, whereas the size of this array depends on the data in the BITMAPINFOHEADER. Depending on the bitmap bitness, this array should have the following length:
1/4/8: the array size should be 2^bitness. I.e. 2/16/256 respectively. The bitmap is considered indexed, and the values in this array define the actual colors.
16: The pixel values translate into colors using so-called bitfields. The array size depends on biCompression member:
BI_RGB: the array should be empty. Default bitfields 5-5-5 are used.
BI_BITFIELDS: The array should have 3 entries. The define the appropriate bitmasks for R/G/B channels.
32: The pixel values either directly correspond to the colors, or translate using bitfields if biCompression is set to BI_BITFIELDS. As with 16-bit case, the array should be either empty or have 3 entries.
The BITMAPINFO structure consists of the BITMAPINFO structure (bmiHeader), and bmiColors, which always has one entry. Which is never the case.
That's why BITMAPINFO is actually a fake structure. In order to create the bitmap header one should first allocate the needed amount of memory for the BITMAPINFOHEADER and the needed array, and then cast it to the BITMAPINFO.
In simple words: comparing BITMAPINFO structures (i.e. using sizeof(BITMAPINFO)) doesn't make sense. The bmiColors will either contain uninitialized data, or be inaccessible, or will actually have larger size.
P.S. BTW, the whole bitmap comparison is somewhat dirty IMHO. Saving the bitmap to the file, just to compare - looks insane. Also you don't actually need to allocate the memory for the whole bitmap, it may be compared line-by-line.
Also, if one of the bitmaps is a DIB, you may directly get pointer to its bits, hence allocating extra memory and copying is not needed.
I believe you could use SoIL Library (or any other than WinApi, actually) for loading and operating on bitmap files. It's free and lightweight, and will shorten your code by about 90%.

How do I create a bitmap using the BITMAPV5HEADER header?

This is my current code, but it causes an error when casting to BITMAPINFOHEADER:
/* Create the bitmap */
BITMAPINFO bmpinfo;
ZeroMemory(&bmpinfo, sizeof(bmpinfo));
BITMAPV5HEADER bmpheader;
ZeroMemory(&bmpheader, sizeof(bmpheader));
bmpheader.bV5Size = sizeof(BITMAPV5HEADER);
bmpheader.bV5Width = width;
bmpheader.bV5Height = height;
bmpheader.bV5Planes = 1;
bmpheader.bV5BitCount = 32;
bmpheader.bV5Compression = BI_BITFIELDS;
bmpheader.bV5SizeImage = width*height*4;
bmpheader.bV5RedMask = 0x00FF0000;
bmpheader.bV5GreenMask = 0x0000FF00;
bmpheader.bV5BlueMask = 0x000000FF;
bmpheader.bV5AlphaMask = 0xFF000000;
bmpheader.bV5CSType = 0x57696e20; // LCS_WINDOWS_COLOR_SPACE
bmpheader.bV5Intent = LCS_GM_BUSINESS;
bmpinfo.bmiHeader = reinterpret_cast<BITMAPINFOHEADER>(bmpheader);
void* converted = NULL;
HDC screen = GetDC(NULL);
HBITMAP result = CreateDIBSection(screen, &bmpinfo, DIB_RGB_COLORS, &converted, NULL, 0);
ReleaseDC(NULL, screen);
// Image data filled here
How will I be able to do this successfully?
BITMAPINFO isn't a true structure, it's more just documentation about how the color map follows the header. Just pass your BITMAPV5HEADER directly to CreateDIBSection:
HBITMAP result = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO *>(&bmpheader), DIB_RGB_COLORS, &converted, NULL, 0);