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%.
Related
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;
}
I have an application function that triggers screenshot capture of the said application's window.
It goes like this:
void PlatformWindow::captureScreenshot()
{
WIN32Window *window = (WIN32Window*)&g_window;
if (window) {
HWND handle = window->getWindow();
if (handle){
RECT client_rect = { 0 };
GetClientRect(handle, &client_rect);
int width = client_rect.right - client_rect.left;
int height = client_rect.bottom - client_rect.top;
HDC hdcScreen = GetDC(handle);
HDC hdc = CreateCompatibleDC(hdcScreen);
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, width, height);
SelectObject(hdc, hbmp);
BitBlt(hdc, 0, 0, width, height, hdcScreen, 0, 0, SRCCOPY);
BITMAPINFO bmp_info = { 0 };
bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader);
bmp_info.bmiHeader.biWidth = width;
bmp_info.bmiHeader.biHeight = height;
bmp_info.bmiHeader.biPlanes = 1;
bmp_info.bmiHeader.biBitCount = 24;
bmp_info.bmiHeader.biCompression = BI_RGB;
int bmp_padding = (width * 3) % 4;
if (bmp_padding != 0) bmp_padding = 4 - bmp_padding;
BYTE *bmp_pixels = new BYTE[(width * 3 + bmp_padding) * height];;
GetDIBits(hdc, hbmp, 0, height, bmp_pixels, &bmp_info, DIB_RGB_COLORS);
BITMAPFILEHEADER bmfHeader;
//Make screenshot name as a time
time_t currentTime = std::time(NULL);
std::ostringstream oss;
auto tm = *std::localtime(¤tTime);
oss << std::put_time(&tm, "%d-%m-%Y_%H-%M");
auto time_string = oss.str();
uint id = 0;
std::string name = "screens\\" + time_string +"_0.bmp";
//Loop over its indexes
while(true){
name = "screens\\" + time_string + "_" + std::to_string(id) +".bmp";
if (!file_exists(name)){
break;
}
id++;
}
LPSTR fileName = const_cast<char *>(name.c_str());
HANDLE bmp_file_handle = 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
DWORD dwSizeofDIB = (width * 3 + bmp_padding) * height + 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(bmp_file_handle, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
WriteFile(bmp_file_handle, (LPSTR)&bmp_info.bmiHeader, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
WriteFile(bmp_file_handle, (LPSTR)bmp_pixels, (width * 3 + bmp_padding) * height, &dwBytesWritten, NULL);
//Close the handle for the file that was created
CloseHandle(bmp_file_handle);
DeleteDC(hdc);
DeleteObject(hbmp);
ReleaseDC(NULL, hdcScreen);
delete[] bmp_pixels;
}
}
}
And it works fine on several machines (Windows 10, XP and so on).
There is, however, a rare case on Windows 7 (and maybe others, I don't know if that's just a bad luck or whatever) that makes screenshot's blank. Just all white.
I ran some diagnosis and am pretty convenient that it, for sure, captures right window, but somehow it does not capture pixels well.
I dig deeper and found out, that whenever I set this option in windows -> performance options -> "Adjust for best performance", it suddenly starts to work and a screenshot is positively taken (no more white screen, which is great).
What I am wondering right now is if I can somehow make my code better to cover up those situations since forcing user to change his Window's options is not an ideal scenario.
#EDIT:
I found out that this is the very option that makes it works, if I disable desktop composition, it works just fine.
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);
.....
}
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 am doing a program where you take a screenshot of a window and then scan every pixel of that picture. But I have a problem assigning RGBQUAD array to the taken screen. Every pixel has the same RGB which is 205. Here is a piece of my code:
RGBQUAD *pixel = malloc((ssWidth * ssHeight)* sizeof(RGBQUAD));
hdcScreen = GetDC(gameHandle);
hdc = CreateCompatibleDC(hdcScreen);
hBmp = CreateCompatibleBitmap(hdcScreen, ssWidth, ssHeight);
SelectObject(hdc, hBmp);
BitBlt(hdc, 0, 0, ssWidth, ssHeight, hdcScreen, xCenter, yCenter, SRCCOPY);
GetDIBits(hdc, hBmp, 0, ssHeight, pixel, &bmpInfo, DIB_RGB_COLORS);
int p = -1;
for(y_var = 0; y_var < ssWidth; y_var++)
{
for(x_var = 0; x_var < ssHeight; x_var++)
{
if(ComparePixel(&pixel[++p]))
{
SetCursorPos(xCenter + x_var + 3, yCenter + y_var + 3);
}
}
}
bool ComparePixel(RGBQUAD *pixel)
{
printf("%d, %d, %d\n"; pixel -> rgbRed, pixel -> rgbGreen, pixel -> rgbBlue);
return false;
}
ComparePixel(RGBQUAD *pixel) function just checks the RGB values. How do i assign the RGBQUAD to the bitmap of the screenshot?
Multiple issues.
The RGBQUAD **pixel = malloc(... and free(*pixel) appear to be the problem. I think you want RGBQUAD *pixel = malloc((ssWidth * ssHeight)* sizeof(RGBQUAD)); (only 1 *)
Suspect the pixels in GetDIBits() s/b pixel.
I think you want y_var = 0; (x_var = 0; also)
ComparePixel() is not defined, but I think you want something closer to if(ComparePixel(pixel[x_var+(y_var*ssWidth)], the_pixel_to_compare_against))
The free(*pixel); s/b _after the 2 for loops and should be free(pixel);