4K screen capturing in Windows and directly save into a buffer - c++

I know there are many posts across the web to do screen capturing in Windows either using GDI or DirectX approaches. However, all I found save the captured image to a bitmap, whereas I want to save it into a buffer instead. Here is my code to do so in GDi way:
HWND hwind = GetDesktopWindow();
HDC hdc = GetDC(hwind);
uint32_t resx = GetSystemMetrics(SM_CXSCREEN);
uint32_t resy = GetSystemMetrics(SM_CYSCREEN);
uint32_t BitsPerPixel = GetDeviceCaps(hdc, BITSPIXEL);
HDC hdc2 = CreateCompatibleDC(hdc);
BITMAPINFO info;
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info.bmiHeader.biWidth = resx;
info.bmiHeader.biHeight = resy;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biBitCount = BitsPerPixel;
info.bmiHeader.biCompression = BI_RGB;
void *data;
static HBITMAP hbitmap = CreateDIBSection(hdc2, &info, DIB_RGB_COLORS,
(void**)&data, 0, 0);
SelectObject(hdc2, hbitmap);
BitBlt(hdc2, 0, 0, resx, resy, hdc, 0, 0, SRCCOPY);
uint8_t *ptr = new uint8_t[4 * resx * resy];
uint32_t lineSizeSrc = 4 * resx; // not always correct
uint32_t linesizeDst = 4 * resx;
for (uint32_t y = 0; y < resy; y++)
memcpy(ptr + y * lineSizeDst,
(uint8_t*) data + y * lineSizeSrc,
lineSizeDst);
DeleteObject(hbitmap);
ReleaseDC(hwind, hdc);
if (hdc2) {
DeleteDC(hdc2);
}
First, as far as I know, the value of lineSizeSrc in this code is not always correct since depending on the screen resolution, some zeros may be added to each line of data. Can anyone please explain when the zeros are added and how to get the correct value for lineSizeSrc?
Second, is it possible to get the captured image in 4K resolution regardless of the resolution of the monitor, for instance by forcing the graphics card to output in 4K resolution?

First, as far as I know, the value of lineSizeSrc in this code is not always correct since depending on the screen resolution, some zeros may be added to each line of data. Can anyone please explain when the zeros are added and how to get the correct value for lineSizeSrc?
The bitmap format requires that each line begin at an address that's a multiple of 4 bytes. Often, this just works out because common image widths are multiples of 4 or because the size of an individual pixel is 32-bits (which is 4 bytes).
But if you're representing an image with an unusual width (e.g., 31 pixels wide) and using something like 24 bits (3 bytes) per pixel then you'll need to pad the end of each line so that the next line starts on a multiple of 4.
A common way to do this is to round up the "stride":
lineSizeSrc = (resx * BitsPerPixel + 31) / 8;
resx * BitsPerPixel tells us the number of bits needed to represent the line. Dividing by 8 converts bits to bytes--sort of. Integer division truncates any remainder. By adding 31 first we ensures that the truncation gives us the smallest multiple of 32 bits (4 bytes) that's equal to or larger than the number of bits we need. So lineSizeSrc is the number of bytes needed for each row.
You should use lineSizeSrc instead of resx in the calculation of how many bytes you need.
Second, is it possible to get the captured image in 4K resolution regardless of the resolution of the monitor, for instance by forcing the graphics card to output in 4K resolution?
There's not a simple, works-in-all-cases method. Your best bet is probably to ask the program to render to a window that's 4K, even if the graphics card isn't in that mode. Some programs will support this, but others might now. Look at the documentation for the WM_PRINT and WM_PRINTCLIENT messages.

Most modern monitors support 32bit color which is relatively simple as it doesn't require palettes. Example in C++:
void capture(char* &buffer)
{
HWND hwnd = GetDesktopWindow();
HDC hdc = GetDC(hwnd);
int w = GetSystemMetrics(SM_CXSCREEN);
int h = GetSystemMetrics(SM_CYSCREEN);
int BitsPerPixel = GetDeviceCaps(hdc, BITSPIXEL);
if (BitsPerPixel = 32)
{
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP bmp = CreateCompatibleBitmap(hdc, w, h);
HGDIOBJ oldbitmap = SelectObject(memdc, bmp);
BitBlt(memdc, 0, 0, w, h, hdc, 0, 0, CAPTUREBLT | SRCCOPY);
SelectObject(memdc, oldbitmap);
DWORD bitsize = w * h * 4;
char *bits = new char[bitsize];
DWORD szInfoHdr = sizeof(BITMAPINFOHEADER);
BITMAPINFOHEADER bmpInfoHeader =
{ szInfoHdr, w, h, 1, (WORD)BitsPerPixel, BI_RGB, 0, 0, 0, 0, 0 };
GetDIBits(hdc, bmp, 0, h, bits, (BITMAPINFO*)&bmpInfoHeader, DIB_RGB_COLORS);
buffer = new char[bitsize + szInfoHdr];
memcpy(buffer, &bmpInfoHeader, szInfoHdr);
memcpy(buffer + szInfoHdr, bits, bitsize);
delete[]bits;
DeleteObject(bmp);
DeleteObject(memdc);
}
ReleaseDC(hwnd, hdc);
}
You can pass buffer through a function. The following code can be used for testing:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
char *buffer = 0;
//capture the screen and save to buffer
capture(buffer);
if (buffer)
{
//paint the buffer for testing:
BITMAPINFO* bmpinfo = (BITMAPINFO*)buffer;
if (bmpinfo->bmiHeader.biBitCount == 32)
{
int w = bmpinfo->bmiHeader.biWidth;
int h = bmpinfo->bmiHeader.biHeight;
char *bits = buffer + sizeof(BITMAPINFOHEADER);
HBITMAP hbitmap = CreateDIBitmap(hdc,
&bmpinfo->bmiHeader, CBM_INIT, bits, bmpinfo, DIB_RGB_COLORS);
HDC memdc = CreateCompatibleDC(hdc);
SelectObject(memdc, hbitmap);
BitBlt(hdc, 0, 0, w, h, memdc, 0, 0, SRCCOPY);
}
delete[]buffer;
}
EndPaint(hWnd, &ps);
}
Note however, GetSystemMetrics(SM_CXSCREEN) returns the width of the primary monitor only.
You may want SM_CXVIRTUALSCREEN and SM_CYVIRTUALSCREEN to get the width/height of multi-monitor. Use SM_(X/Y)VIRTUALSCREEN to get top-left corner.

Related

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

How can an HDC bitmap be copied to a 3-dimensional array quickly?

I'm storing image rgb data from an HDC bitmap in a 3d array by iterating through each pixel using GetPixel(hdc, i, j).
It works but this function is incredibly slow, however. Even for large images (1920x1080=6,220,800 values, excluding alpha), it should not be taking as long as it is.
I've looked online for alternatives to this but none of them are very clean / readable, at least to me.
Basically I want an hdc bitmap to be copied to an unsigned char the_image[rows][columns][3] more quickly.
Here is the current code. I need help improving the code under //store bitmap in array
// copy window to bitmap
HDC hScreen = GetDC(window);
HDC hDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, 256, 256);
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
BOOL bRet = BitBlt(hDC, 0, 0, 256, 256, hScreen, 0, 0, SRCCOPY);
//store bitmap in array
unsigned char the_image[256][256][3];
COLORREF pixel_color;
for (int i = 0; i < 256; i++) {
for (int j = 0; j < 256; j++) {
pixel_color = GetPixel(hDC, i, j);
the_image[i][j][0] = GetRValue(pixel_color);
the_image[i][j][1] = GetGValue(pixel_color);
the_image[i][j][2] = GetBValue(pixel_color);
}
}
// clean up
SelectObject(hDC, old_obj);
DeleteDC(hDC);
ReleaseDC(NULL, hScreen);
DeleteObject(hBitmap);
Thanks to Raymond Chen for introducing the "GetDIBits" function, and this other thread, I finally managed to get it working.
It's pretty much instantaneous compared to before, although I'm getting some problems with exceeding stack size for large images, should be a fairly easy fix though. Here's the code that replaces what's under "//store bitmap in array":
BITMAPINFO MyBMInfo = { 0 };
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
GetDIBits(hDC, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS);
MyBMInfo.bmiHeader.biBitCount = 24;
MyBMInfo.bmiHeader.biCompression = BI_RGB;
MyBMInfo.bmiHeader.biHeight = abs(MyBMInfo.bmiHeader.biHeight);
unsigned char the_image[256][256][3];
GetDIBits(hDC, hBitmap, 0, MyBMInfo.bmiHeader.biHeight,
&the_image[0], &MyBMInfo, DIB_RGB_COLORS);

Copying from a packed DIB void pointer

I am working on an API to cache GDI objects, written in C++, in which I am implementing different Create methods that imitate the win32 API. One such method is CreateDIBPatternBrushPt(), which takes in a VOID* to a packed DIB. The VOID* contains a BITMAPINFO structure followed immediately by an array of bytes defining the pixels of the bitmap. I did some research on packed DIBs. According to the book, Programming Windows, as well as Wikipedia the length is equal to the product of the row length and biHeight (a member of BITMAPINFOHEADER), where:
RowLength = 4 * ((bmi.bcWidth * bmi.bcBitCount + 31) / 32) ;
So, currently my thought is to do something like this:
//copy BIMAPINFO to access width and height
//lpPackedDIB is the VOID* parameter of CreateDIBPatternBrushPt()
BITMAPINFO tmpBitmapInfo;
std::memcpy(&tmpBitmapInfo, lpPackedDIB, sizeof(BITMAPINFO));
//copy entire packed DIB
int rowLength = 4 * ((tmpBitmapInfo.bmiHeader.biWidth * tmpBitmapInfo.bmiHeader.biBitCount + 31) / 32);
std::memcpy(m_pPackedDIB, lpPackedDIB, sizeof(BITMAPINFO) + rowLength * std::abs(bitmapInfo.bmiHeader.biHeight));
Does this seem like a valid way to do this? I would also like to know where lpPackedDIB would come from for people who use CreateDIBPatternBrushPt(), so that I can properly test this logic.
EDIT:
References:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd183493(v=vs.85).aspx
https://en.wikipedia.org/wiki/BMP_file_format#File_structure
https://www-user.tu-chemnitz.de/~heha/petzold/ch15b.htm (particularly "The DIB Pixel Bits" section)
This answer was posted before, but taken down for some reason. It doesn't directly address the question, but it gives good insight to the problem I was facing. Credit to user: Barmak Shemirani.
You can simply use CreatePatternBrush to get HBRUSH from HBITAMP
Or use CreateDIBPatternBrushPt. However, note that packed dib is only 14 bytes smaller, since it doesn't need BITMAPFILEHEADER. This makes it useful for very small brushes. For example a 2x1 green/black brush:
typedef struct
{
BITMAPINFOHEADER header;
unsigned char bits[2 * 1 * 4];
} DIBSTRUCT;
const DIBSTRUCT dib =
{
{ sizeof(BITMAPINFOHEADER), 2, 1, 1, 32, BI_RGB, 0, 0, 0, 0, 0 },
{ 0,0xff,0,0, 0,0,0,0 }
};
HBRUSH hbrush = CreateDIBPatternBrushPt(&dib, DIB_RGB_COLORS);
You can make the brush from bitmap file as well. Allocate the dib, add room for BITMAPINFOHEADER and color table. Then copy BITMAPINFOHEADER, followed by color table (if any), followed by pixels. Example:
HBRUSH getbrush(HBITMAP hbitmap)
{
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
int colorCount = 0;
if (bm.bmBitsPixel == 1) colorCount = 2;
if (bm.bmBitsPixel == 4) colorCount = 16;
if (bm.bmBitsPixel == 8) colorCount = 256;
RGBQUAD rgb[256];
if (colorCount)
{
HDC memdc = CreateCompatibleDC(NULL);
HGDIOBJ oldbitmap = SelectObject(memdc, hbitmap);
GetDIBColorTable(memdc, 0, colorCount, rgb);
SelectObject(memdc, oldbitmap);
DeleteDC(memdc);
}
BITMAPINFOHEADER bminfo = { sizeof(BITMAPINFOHEADER),
bm.bmWidth, bm.bmHeight, 1, bm.bmBitsPixel, BI_RGB, 0, 0, 0, 0, 0 };
DWORD size = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
char *dib = new char[sizeof(BITMAPINFOHEADER) + colorCount * 4 + size];
memcpy(dib, &bminfo, sizeof(BITMAPINFOHEADER));
memcpy(dib + sizeof(BITMAPINFOHEADER), rgb, colorCount * 4);
memcpy(dib + sizeof(BITMAPINFOHEADER) + colorCount * 4, bm.bmBits, size);
HBRUSH hbrush = CreateDIBPatternBrushPt(dib, DIB_RGB_COLORS);
//clearnup
DeleteObject(hbitmap);
delete[]dib;
return hbrush;
}

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 to read the screen pixels?

I want to read a rectangular area, or whole screen pixels. As if screenshot button was pressed.
How i do this?
Edit: Working code:
void CaptureScreen(char *filename)
{
int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
HWND hDesktopWnd = GetDesktopWindow();
HDC hDesktopDC = GetDC(hDesktopWnd);
HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
HBITMAP hCaptureBitmap = CreateCompatibleBitmap(hDesktopDC, nScreenWidth, nScreenHeight);
SelectObject(hCaptureDC, hCaptureBitmap);
BitBlt(hCaptureDC, 0, 0, nScreenWidth, nScreenHeight, hDesktopDC, 0,0, SRCCOPY|CAPTUREBLT);
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = nScreenWidth;
bmi.bmiHeader.biHeight = nScreenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
RGBQUAD *pPixels = new RGBQUAD[nScreenWidth * nScreenHeight];
GetDIBits(
hCaptureDC,
hCaptureBitmap,
0,
nScreenHeight,
pPixels,
&bmi,
DIB_RGB_COLORS
);
// write:
int p;
int x, y;
FILE *fp = fopen(filename, "wb");
for(y = 0; y < nScreenHeight; y++){
for(x = 0; x < nScreenWidth; x++){
p = (nScreenHeight-y-1)*nScreenWidth+x; // upside down
unsigned char r = pPixels[p].rgbRed;
unsigned char g = pPixels[p].rgbGreen;
unsigned char b = pPixels[p].rgbBlue;
fwrite(fp, &r, 1);
fwrite(fp, &g, 1);
fwrite(fp, &b, 1);
}
}
fclose(fp);
delete [] pPixels;
ReleaseDC(hDesktopWnd, hDesktopDC);
DeleteDC(hCaptureDC);
DeleteObject(hCaptureBitmap);
}
Starting with your code and omitting error checking ...
// Create a BITMAPINFO specifying the format you want the pixels in.
// To keep this simple, we'll use 32-bits per pixel (the high byte isn't
// used).
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = nScreenWidth;
bmi.bmiHeader.biHeight = nScreenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
// Allocate a buffer to receive the pixel data.
RGBQUAD *pPixels = new RGBQUAD[nScreenWidth * nScreenHeight];
// Call GetDIBits to copy the bits from the device dependent bitmap
// into the buffer allocated above, using the pixel format you
// chose in the BITMAPINFO.
::GetDIBits(hCaptureDC,
hCaptureBitmap,
0, // starting scanline
nScreenHeight, // scanlines to copy
pPixels, // buffer for your copy of the pixels
&bmi, // format you want the data in
DIB_RGB_COLORS); // actual pixels, not palette references
// You can now access the raw pixel data in pPixels. Note that they are
// stored from the bottom scanline to the top, so pPixels[0] is the lower
// left pixel, pPixels[1] is the next pixel to the right,
// pPixels[nScreenWidth] is the first pixel on the second row from the
// bottom, etc.
// Don't forget to free the pixel buffer.
delete [] pPixels;
Rereading your question, it sounds like we may have gotten off on a tangent with the screen capture. If you just want to check some pixels on the screen, you can use GetPixel.
HDC hdcScreen = ::GetDC(NULL);
COLORREF pixel = ::GetPixel(hdcScreen, x, y);
ReleaseDC(NULL, hdcScreen);
if (pixel != CLR_INVALID) {
int red = GetRValue(pixel);
int green = GetGValue(pixel);
int blue = GetBValue(pixel);
...
} else {
// Error, x and y were outside the clipping region.
}
If you're going to read a lot of pixels, then you're better off with a screen capture and then using GetDIBits. Calling GetPixel zillions of times will be slow.
You make a screenshot with BitBlt(). The size of the shot is set with the nWidth and nHeight arguments. The upper left corner is set with the nXSrc and nYSrc arguments.
You can use the code below to read the screen pixels:
HWND desktop = GetDesktopWindow();
HDC desktopHdc = GetDC(desktop);
COLORREF color = GetPixel(desktopHdc, x, y);
HBITMAP is not a pointer or an array, it is a handle that is managed by Windows and has meaning only to Windows. You must ask Windows to copy the pixels somewhere for use.
To get an individual pixel value, you can use GetPixel without even needing a bitmap. This will be slow if you need to access many pixels.
To copy a bitmap to memory you can access, use the GetDIBits function.