How to compare byte to byte of two bitmap screenshots in c++ - c++

IN THE END OF THE QUESTION MY LAST EDIT
Hi all,
I have to implement a function that compares two shots of a portion of the screen in order to know if there are differences/variations. I wrote something like the following code but I can't manage it to work. In the code COORDINATES_RECT is a struct
typedef struct _COORDINATES_RECT {
int x;
int y;
int sizeX;
int sizeY;
} COORDINATES_RECT;
that in input holds data to know which is the portion of the screen to analyze, and in output returns the data of the biggest rectangle in which the function found variations. To better explain the problem, I saw that if construct in the end:
if(lpbitmap1[(i*bmpScreen1.bmWidth)+j] != lpbitmap2[(i*bmpScreen1.bmWidth)+j])
never get executed. I don't know if it is the right way to compare two bitmaps (converted into char arrays). I googled and searched in msdn but with no result.
Full code follows:
void testBitmapVariations(COORDINATES_RECT *c)
{
HDC hdcScreen = GetDC(NULL);
HDC hdcMemDC1 = CreateCompatibleDC(hdcScreen);
HDC hdcMemDC2 = CreateCompatibleDC(hdcScreen);
HBITMAP hBmpScreen1 = NULL;
HBITMAP hBmpScreen2 = NULL;
BITMAP bmpScreen1;
BITMAP bmpScreen2;
if(!hdcMemDC1 || !hdcMemDC2)
{
MessageBox(NULL,L"CreateCompatibleDC failed", L"Failed", MB_OK);
ReleaseDC(NULL, hdcMemDC1);
ReleaseDC(NULL, hdcMemDC2);
return;
}
hBmpScreen1 = CreateCompatibleBitmap(hdcMemDC1, c->sizeX, c->sizeY);
hBmpScreen2 = CreateCompatibleBitmap(hdcMemDC2, c->sizeX, c->sizeY);
SelectObject(hdcMemDC1,hBmpScreen1);
if(!BitBlt(hdcMemDC1,
0,0,
c->sizeX, c->sizeY,
hdcScreen,
c->x,c->y,
SRCCOPY))
{
MessageBox(NULL,L"BitBlt failed", L"Failed", MB_OK);
ReleaseDC(NULL, hdcMemDC1);
ReleaseDC(NULL, hdcMemDC2);
return;
}
GetObject(hBmpScreen1,sizeof(BITMAP),&bmpScreen1);
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bmpScreen1.bmWidth;
bi.biHeight = bmpScreen1.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
DWORD dwBmpSize = ((bmpScreen1.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen1.bmHeight;
// Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that
// call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc
// have greater overhead than HeapAlloc.
char *lpbitmap1 = (char *)malloc(dwBmpSize);
// Gets the "bits" from the bitmap and copies them into a buffer
// which is pointed to by lpbitmap1.
GetDIBits(hdcScreen, hBmpScreen1, 0,
(UINT)bmpScreen1.bmHeight,
lpbitmap1,
(BITMAPINFO *)&bi, DIB_RGB_COLORS);
Sleep(200);
SelectObject(hdcMemDC2,hBmpScreen2);
if(!BitBlt(hdcMemDC2,
0,0,
c->sizeX, c->sizeY,
hdcScreen,
c->x,c->y,
SRCCOPY))
{
MessageBox(NULL,L"BitBlt failed", L"Failed", MB_OK);
ReleaseDC(NULL, hdcMemDC1);
ReleaseDC(NULL, hdcMemDC2);
return;
}
GetObject(hBmpScreen2,sizeof(BITMAP),&bmpScreen2);
char *lpbitmap2 = (char *)malloc(dwBmpSize);
// Gets the "bits" from the bitmap and copies them into a buffer
// which is pointed to by lpbitmap2.
GetDIBits(hdcScreen, hBmpScreen2, 0,
(UINT)bmpScreen2.bmHeight,
lpbitmap2,
(BITMAPINFO *)&bi, DIB_RGB_COLORS);
int i, j, minX = bmpScreen1.bmWidth, minY = bmpScreen1.bmHeight, maxX = 0, maxY = 0;
bool changed = false;
for(i = 0; i < bmpScreen1.bmHeight; i++)
{
for(j = 0; j < bmpScreen1.bmWidth; j++)
{
// I don't know why this if never get executed
if(lpbitmap1[(i*bmpScreen1.bmWidth)+j] != lpbitmap2[(i*bmpScreen1.bmWidth)+j])
{
changed = true;
if(i < minY)
minY = i;
if(i > maxY)
maxY = i;
if(j < minX)
minX = j;
if(j > maxY)
maxY = j;
}
}
}
if(changed)
{
c->x = minX;
c->y = minY;
c->sizeX = maxX - minX;
c->sizeY = maxY - minY;
}
else
{
c->sizeX = 0;
c->sizeY = 0;
}
//Frees from the heap
free(lpbitmap1);
free(lpbitmap2);
ReleaseDC(NULL, hdcMemDC1);
ReleaseDC(NULL, hdcMemDC2);
}
Thanks
Francesco
Ps. Variables declarations are put next to the block they refers to only to achieve more clarity in this question..
EDIT:
I rewrote it like this and it seems to work :) Thanks to all, however any correction is appreciated
void testBitmapVar(COORDINATES_RECT *c)
{
HDC screenDC = GetDC(0),
memDC = CreateCompatibleDC(screenDC);
HBITMAP hBm = CreateCompatibleBitmap(screenDC, c->sizeX, c->sizeY),
oldHBm;
BITMAP bm;
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bi;
UINT *pixels = (UINT*) malloc ((c->sizeX*c->sizeY) * sizeof(UINT));
std::ostringstream ss;
std::wstring str;
int i, j, minX, maxX, minY, maxY;
if(!pixels)
{
c->sizeX = 0;
c->sizeY = 0;
return;
}
memset(pixels, 0, (c->sizeX*c->sizeY) * sizeof(UINT));
oldHBm = (HBITMAP) SelectObject(memDC, hBm);
// copies to bitmap
BitBlt(memDC, 0, 0, c->sizeX, c->sizeY, screenDC, c->x, c->y, SRCCOPY);
Sleep(500);
BitBlt(memDC, 0, 0, c->sizeX, c->sizeY, screenDC, c->x, c->y, SRCINVERT);
GetObject(hBm, sizeof(BITMAP), &bm);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bm.bmWidth;
bi.biHeight = bm.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
GetDIBits(memDC, hBm, 0,
(UINT)bm.bmHeight,
pixels,
(BITMAPINFO *)&bi, DIB_RGB_COLORS);
for(i = 0, minY = c->sizeY, maxY = -1; i < c->sizeY; i++)
{
for(j = 0, minX = c->sizeX, maxX = -1; j < c->sizeX; j++)
{
if(pixels[(i*c->sizeX)+j])
{
if(i < minY)
minY = i;
if(i > maxY)
maxY = i;
if(j < minX)
minX = j;
if(j > maxX)
maxX = j;
}
}
}
if(maxX != -1 && maxY != -1)
{
c->x = minX;
c->y = minY;
c->sizeX = maxX - minX;
c->sizeY = maxY - minY;
}
else
{
c->sizeX = 0;
c->sizeY = 0;
}
free(pixels);
SelectObject(memDC, oldHBm);
DeleteObject(hBm);
ReleaseDC(0, screenDC);
DeleteDC(memDC);
}

Rather than comparing the two bitmaps entirely on your own, consider using BitBlt to combine them using the SRCINVERT operator, which XORs the two together, so the parts that are identical will show up as zeros, and all the non-zero areas will be differences.

gc5, thanks for example! I'll add 2bit of mine:
you should define bi as:
BITMAPINFO bi;
memset(&bi, 0, sizeof(BITMAPINFO));
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = bm.bmWidth;
bi.bmiHeader.biHeight = bm.bmHeight;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB;
and then no need to define a type:
GetDIBits(memDC, hBm, 0, (UINT)bm.bmHeight, pixels, &bi, DIB_RGB_COLORS);

Related

Win32 GetPixel() returns wrong values whereas SetPixel() works fine, even when DPI awareness is turned on

I have been struggling with this issue for the entire day and I am still not making any progress.
CONTEXT
I am trying to reproduce a virtual piano keyboard (driven with MIDI files) on LEDs attached to a physical keyboard so that my wife can learn how to play piano quicker (Christmas gift). Here is a picture of the program:
WHAT THE CODE DOES
(Or is supposed to do) I enter the coordinates of the keyboard as measured in Paint.NET after taking a screenshot of the program full-screen. The user is asked to put the software full screen. Then I calculate the coordinates of the middle of all the keys, and get the colour of the key when it is not activated. Finally I check that all white keys have the same colour, and that the black keys have the same colour (to make sure the keyboard is correctly displayed).
Later, I'll loop forever, getting the colour of each key and lighting up the corresponding LED on the physical keyboard if the colour of that key has changed from the "rest" state.
PROBLEM
I know I pick the colours at the right location, because SetPixel() displays the red pixels spot on in the middle of all keys (as seen in code). However, GetPixel still returns wrong values often times (1/4th of the time for the white keys, always for the black keys). What's happening ?
WHAT I TRIED
A added a manifest file to the project which successfully modified the size of the desktop to the correct value of 1920x1080 - but even though the RGB values changed a bit, they are still off
- I added a captureAndSave() function in case the computer "sees" a different thing than me when I do a screen capture (to obtain all the input coordinates), or than whatever does SetPixel() because this works - but the image is indeed identical, so I don't know what is happening.
- Many debugging runs...
CODE
(skip to VirtualKeyboard::init())
#include <iostream>
#include <windows.h>
using namespace std;
const unsigned int N_WHITE_KEYS = 49;
const unsigned int N_PIANO_KEYS = N_WHITE_KEYS+35;
const unsigned int N_ABSENT_BLK_KEYS = N_WHITE_KEYS*2-N_PIANO_KEYS-1;
const unsigned int ABSENT_BLK_KEYS[N_ABSENT_BLK_KEYS] = {3,7,10,14,17,21,24,28,31,35,38,42,45}; //TODO: find a formula rather than this list
const unsigned int VKxLeft = 19,
VKyTop = 150,
VKxRight = 1731,
VKyBtm = 324;
//TEMP
#include <stdio.h>
bool captureAndSave(const HWND& hWnd, int nBitCount, const char* szFilePath)
{
if(!szFilePath || !strlen(szFilePath))
{
cout << "bad function arguments\n";
return false;
}
//calculate the number of color indexes in the color table
int nColorTableEntries = -1;
switch(nBitCount)
{
case 1:
nColorTableEntries = 2;
break;
case 4:
nColorTableEntries = 16;
break;
case 8:
nColorTableEntries = 256;
break;
case 16:
case 24:
case 32:
nColorTableEntries = 0;
break;
default:
nColorTableEntries = -1;
break;
}
if(nColorTableEntries == -1)
{
cout << "bad bits-per-pixel argument\n";
return false;
}
HDC hDC = GetDC(hWnd);
HDC hMemDC = CreateCompatibleDC(hDC);
int nWidth = 0;
int nHeight = 0;
if(hWnd != HWND_DESKTOP)
{
RECT rect;
GetClientRect(hWnd, &rect);
nWidth = rect.right - rect.left;
nHeight = rect.bottom - rect.top;
}
else
{
nWidth = ::GetSystemMetrics(SM_CXSCREEN);
nHeight = ::GetSystemMetrics(SM_CYSCREEN);
}
HBITMAP hBMP = CreateCompatibleBitmap(hDC, nWidth, nHeight);
SelectObject(hMemDC, hBMP);
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, 0, 0, SRCCOPY);
int nStructLength = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
LPBITMAPINFOHEADER lpBitmapInfoHeader = (LPBITMAPINFOHEADER)new char[nStructLength];
::ZeroMemory(lpBitmapInfoHeader, nStructLength);
lpBitmapInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
lpBitmapInfoHeader->biWidth = nWidth;
lpBitmapInfoHeader->biHeight = nHeight;
lpBitmapInfoHeader->biPlanes = 1;
lpBitmapInfoHeader->biBitCount = nBitCount;
lpBitmapInfoHeader->biCompression = BI_RGB;
lpBitmapInfoHeader->biXPelsPerMeter = 0;
lpBitmapInfoHeader->biYPelsPerMeter = 0;
lpBitmapInfoHeader->biClrUsed = nColorTableEntries;
lpBitmapInfoHeader->biClrImportant = nColorTableEntries;
DWORD dwBytes = ((DWORD) nWidth * nBitCount) / 32;
if(((DWORD) nWidth * nBitCount) % 32) {
dwBytes++;
}
dwBytes *= 4;
DWORD dwSizeImage = dwBytes * nHeight;
lpBitmapInfoHeader->biSizeImage = dwSizeImage;
LPBYTE lpDibBits = 0;
HBITMAP hBitmap = ::CreateDIBSection(hMemDC, (LPBITMAPINFO)lpBitmapInfoHeader, DIB_RGB_COLORS, (void**)&lpDibBits, NULL, 0);
SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, 0, 0, SRCCOPY);
ReleaseDC(hWnd, hDC);
BITMAPFILEHEADER bmfh;
bmfh.bfType = 0x4d42; // 'BM'
int nHeaderSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
bmfh.bfSize = 0;
bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
FILE *pFile = 0;
pFile = fopen(szFilePath, "wb");
if(!pFile)
{
::DeleteObject(hBMP);
::DeleteObject(hBitmap);
delete[]lpBitmapInfoHeader;
cout << "can not open file\n";
return false;
}
DWORD nColorTableSize = 0;
if (nBitCount != 24)
nColorTableSize = (1 << nBitCount) * sizeof(RGBQUAD);
else
nColorTableSize = 0;
fwrite(&bmfh, sizeof(BITMAPFILEHEADER), 1, pFile);
fwrite(lpBitmapInfoHeader, nHeaderSize,1,pFile);
if(nBitCount < 16)
{
int nBytesWritten = 0;
RGBQUAD *rgbTable = new RGBQUAD[nColorTableEntries * sizeof(RGBQUAD)];
//fill RGBQUAD table and write it in file
for(int i = 0; i < nColorTableEntries; ++i)
{
rgbTable[i].rgbRed = rgbTable[i].rgbGreen = rgbTable[i].rgbBlue = i;
rgbTable[i].rgbReserved = 0;
fwrite(&rgbTable[i], sizeof(RGBQUAD), 1, pFile);
}
delete[]rgbTable;
/*
RGBQUAD rgb;
for (DWORD i = 0; i < nColorTableEntries ; i++)
{
rgb.rgbBlue = rgb.rgbGreen = rgb.rgbRed = (BYTE)(i*(255/(nColorTableEntries-1)));
nBytesWritten = fwrite(&rgb, 1, sizeof(rgb), pFile);
if (nBytesWritten != sizeof(rgb))
{
printf("error while writing rgb header\n");
fclose(pFile);
::DeleteObject(hBMP);
::DeleteObject(hBitmap);
delete[]lpBitmapInfoHeader;
return false;
}
}
*/
}
fwrite(lpDibBits, dwSizeImage, 1, pFile);
fclose(pFile);
::DeleteObject(hBMP);
::DeleteObject(hBitmap);
delete[]lpBitmapInfoHeader;
}
//End TEMP
inline bool SameColours(COLORREF const &a, COLORREF const &b) {
bool ret = true;
ret &= GetRValue(a) == GetRValue(b);
ret &= GetGValue(a) == GetGValue(b);
ret &= GetBValue(a) == GetBValue(b);
return ret;
}
class VirtualKeyboard
{
COLORREF keyColUnpressed[N_PIANO_KEYS];
unsigned int keyX[N_PIANO_KEYS]; //White then black, from left to right
unsigned int keyY[N_PIANO_KEYS]; //White then black, from left to right
public:
bool init(unsigned int xLeft, unsigned int yTop, unsigned int xRight, unsigned yBtm) {
bool ret = true;
//Calculate parameters of the virtual keyboard
const float whtKeyHeight = (yBtm-yTop);
const float whtKeyWidth = float(xRight-xLeft)/(N_WHITE_KEYS);
//Calculate coordinates of the white keys
for(unsigned int i = 0; i < N_WHITE_KEYS ; ++i) {
keyX[i]=xLeft+(i+1.f/2)*float(whtKeyWidth);
keyY[i]=yTop+3.f/4*float(whtKeyHeight);
}
//Calculate coordinates of the black keys
unsigned int iBlkKey = 0;
for(unsigned int i = 0 ; i < N_WHITE_KEYS-1 ; ++i) {
//Determine if there is a black key
bool skip = false;
//Some black keys are absent from the offset white keys pattern - skip if applicable
for(unsigned int j = 0 ; j < N_ABSENT_BLK_KEYS ; ++j) {
if(i+1 == ABSENT_BLK_KEYS[j]) {
skip = true;
break;
}
}
//If that key exists, add it to the list
if(!skip) {
keyX[iBlkKey+N_WHITE_KEYS]=xLeft+whtKeyWidth*(i+1);
keyY[iBlkKey+N_WHITE_KEYS]=yTop+1.f/4*float(whtKeyHeight);
iBlkKey++;
}
}
//Capture the screen
HDC hdcScreen = ::GetDC(GetDesktopWindow());
captureAndSave(GetDesktopWindow(),32,"./capture.bmp");
//And fill in the colors "at rest" for all the keys
for(unsigned int i = 0 ; i < N_PIANO_KEYS ; ++i) {
keyColUnpressed[i] = ::GetPixel(hdcScreen, keyX[i], keyY[i]);
unsigned int r = GetRValue(keyColUnpressed[i]);
unsigned int g = GetGValue(keyColUnpressed[i]);
unsigned int b = GetBValue(keyColUnpressed[i]);
::SetPixel(hdcScreen, keyX[i], keyY[i], RGB(255,0,0)); //DEBUG: Breakpoint on this line, the RGB values are wrong for some keys (e.g. i=8 and all blacks)
Sleep(100);
}
ReleaseDC(GetDesktopWindow(),hdcScreen);
//Sanity check : all white keys should have the same colour, and all black keys their own colour as well
for(unsigned int i = 1 ; i < N_PIANO_KEYS ; ++i) {
if(i != 1 && i != N_WHITE_KEYS) {
if(
!SameColours(
keyColUnpressed[i],(i < N_WHITE_KEYS ? keyColUnpressed[0]
: keyColUnpressed[N_WHITE_KEYS])
)
)
{
ret = false;
break;
}
}
}
return 0;
}
};
int main()
{
VirtualKeyboard vk;
cout << "You have 3 seconds to minimize this window and maximise MidiSheetMusic" << endl;
Sleep(3000);
cout << "Result of init() : " << vk.init(VKxLeft, VKyTop, VKxRight, VKyBtm) << endl;
while(1)
{
//Get all keys pixels and reproduce on the LEDs on the physical keyboard
Sleep(10); //Runs while not Ctrl+c
}
return 0;
}
Thank you very much in advance.

GetPixel is WAY too slow

I have a bunch of squares and each has a specific identity/symbol I need to identify. So far I have something like:
#include <iostream>
#include <windows.h>
using namespace std;
int main() {
HDC dc = GetDC(0);
COLORREF color;
int sum, x, y;
while (true) {
sum = 0;
Sleep(100);
for (x = 512; x < 521; x++) {
for (y = 550; y < 565; y++) {
color = GetPixel(dc, x, y);
sum = GetRValue(color) + GetBValue(color) + GetGValue(color);
}
}
cout << "SUM: " << sum << endl;
}
return 0;
}
Obviously it only scans one block so far. The problem is somehow even though it's only just over 100 pixels, it takes an INSANELY long time. I can't even imagine what could be going on. It takes well over a second, maybe two seconds, for each repetition. What can I do? There has to be a faster way to do this. If I can't query individual pixels, would there be a way to get a region of the screen? The zone is not inside my program's window.
Jonathan comments in the question to use DIB, but there's no answer showing how. For the sake of completeness, here's Mister B's code:
COLORREF getcolor(POINT pt) {
HDC hDc = GetDC(0);
HDC hDcmem = CreateCompatibleDC(0);
HBITMAP hBmp = CreateCompatibleBitmap(hDc, 1, 1);
SelectObject(hDcmem, hBmp);
BitBlt(hDcmem, 0, 0, 1, 1, hDc, pt.x, pt.y, SRCCOPY);
LPBITMAPINFO lpbmi = new BITMAPINFO;
lpbmi->bmiHeader.biBitCount = 24;
lpbmi->bmiHeader.biCompression = BI_RGB;
lpbmi->bmiHeader.biPlanes = 1;
lpbmi->bmiHeader.biHeight = 1;
lpbmi->bmiHeader.biWidth = 1;
lpbmi->bmiHeader.biSize = sizeof(BITMAPINFO);
BYTE lpvBits[4];
GetDIBits(hDcmem, hBmp, 0, 1, lpvBits, lpbmi, DIB_RGB_COLORS);
COLORREF currColor = RGB(lpvBits[2], lpvBits[1], lpvBits[0]);
delete lpbmi;
DeleteObject(hBmp);
DeleteDC(hDcmem);
ReleaseDC(0, hDc);
return currColor;
}
This is still very slow as Ben points out in a comment, and the solution is to use these building blocks to copy the entire area at once. Your code would become something like this:
#include <iostream>
#include <vector>
#include <windows.h>
int main() {
int const startX = 512;
int const endX = 521;
int const startX = 550;
int const endY = 565;
int const width = endX - startX;
int const height = endY - startY;
HDC const hDc = GetDC(0);
HDC const hDcmem = CreateCompatibleDC(0);
HBITMAP const hBmp = CreateCompatibleBitmap(hDc, width, height);
auto const oldBmp = SelectObject(hDcmem, hBmp);
BITMAPINFO bmi{};
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biHeight = height;
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biSize = sizeof(BITMAPINFO);
std::vector<RGBQUAD> pixels(height * width);
while (true) {
Sleep(100);
BitBlt(hDcmem, 0, 0, width, height, hDc, startX, startY, SRCCOPY);
GetDIBits(hDcmem, hBmp, 0, height, &pixels[0], &bmi, DIB_RGB_COLORS);
int sum = 0;
for (int i = 0; i < height * width; ++i) {
sum = pixels[i].R + pixels[i].G + pixels[i].B;
}
std::cout << "SUM: " << sum << std::endl;
}
SelectObject(hDcmem, oldBmp);
DeleteObject(hBmp);
DeleteDC(hDcmem);
ReleaseDC(0, hDc);
return 0;
}

CreateDIBSection graphical artifacts or how to access bitmap bits correctly

UPDATE:
Thanks to Denis Anisimov I solved my problem by replacing:
for (int i = 0; i < Width * Height; i++)
{
pRgb[0] = RValue;
pRgb[1] = GValue;
pRgb[2] = BValue;
pRgb += 3;
}
with:
int BytesPerLine = Width * 3;
if (BytesPerLine % 4 != 0)
BytesPerLine += 4 - BytesPerLine % 4;
PBYTE Line = NULL;
for (int y = 0; y < Height; y++)
{
Line = pRgb;
for (int x = 0; x < Width; x++)
{
Line[0] = RValue;
Line[1] = GValue;
Line[2] = BValue;
Line += 3;
}
pRgb += BytesPerLine;
}
When creating device independent bitmap I sometimes get strange line artifacts. It only occurs when I change window, in which I draw, width. During resize I delete old bitmap and create new using custom function. Because I have direct access to bitmap's bits, I change their colors directly, but that is where problem occurs (I think), because FillRect works perfectly. I may be not accessing bits pointer correctly. Here are pictures when it works and other with artifacts:
This code shows how I draw in window:
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
Painter Paint;
if (temp != NULL)
{
DeleteObject(temp);
temp = NULL;
}
temp = Paint.Create24BitBitmap(NULL, ps.rcPaint.right - ps.rcPaint.left - 5, ps.rcPaint.bottom - ps.rcPaint.top - 5, 20, 70, 0);
HDC MemoryDC = CreateCompatibleDC(ps.hdc);
SelectObject(MemoryDC, temp);
BitBlt(ps.hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left - 5, ps.rcPaint.bottom - ps.rcPaint.top - 5, MemoryDC, 0, 0, SRCCOPY);
DeleteDC(MemoryDC);
EndPaint(hWnd, &ps);
}
break;
case WM_NCPAINT:
{
RedrawWindow(hWnd, NULL, NULL, RDW_ERASENOW | RDW_INVALIDATE | RDW_INTERNALPAINT);
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
break;
And this one shows how I create bitmap:
HBITMAP Create24BitBitmap(HDC *hDC, int Width, int Height, BYTE RValue, BYTE GValue, BYTE BValue)
{
Width = ((Width < 1)?1:Width);
Height = ((Height < 1)?1:Height);
RValue = ((RValue > 254)?255:((RValue < 0)?0:RValue));
GValue = ((GValue > 254)?255:((GValue < 0)?0:GValue));
BValue = ((BValue > 254)?255:((BValue < 0)?0:BValue));
HDC MemoryDC = NULL;
if (hDC != NULL)
MemoryDC = CreateCompatibleDC(*hDC);
else
MemoryDC = CreateCompatibleDC(NULL);
BITMAPINFO BitmapInfo;
ZeroMemory(&BitmapInfo,sizeof(BITMAPINFO));
BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BitmapInfo.bmiHeader.biWidth = Width;
BitmapInfo.bmiHeader.biHeight = Height;
BitmapInfo.bmiHeader.biPlanes = 1;
BitmapInfo.bmiHeader.biBitCount = 24;
BitmapInfo.bmiHeader.biCompression = BI_RGB;
PBYTE pRgb = NULL;
HBITMAP hBitmap = CreateDIBSection(MemoryDC, &BitmapInfo, DIB_RGB_COLORS, (void**)&pRgb, NULL, NULL);
DeleteDC(MemoryDC);
if (hBitmap == NULL)
{
MessageBox(NULL, TEXT("\"Create24BitBitmap\" function failed."), TEXT("ERROR!"), MB_ICONERROR);
exit(EXIT_FAILURE);
}
for (int i = 0; i < Width * Height; i++)
{
pRgb[0] = RValue;
pRgb[1] = GValue;
pRgb[2] = BValue;
pRgb += 3;
}
return hBitmap;
}
So the question would be how to correctly access bitmap bits? Or are there other things that may cause this artifact?
Here you can find compiled program:
http://www.putlocker.com/file/D555A03E41032615
This code is wrong:
for (int i = 0; i < Width * Height; i++)
{
pRgb[0] = RValue;
pRgb[1] = GValue;
pRgb[2] = BValue;
pRgb += 3;
}
Start of every line of bitmap must be aligned to 4-byte boundaries.
Update:
You should use something like this:
BytesPerLine := Width * 3;
if BytesPerLine mod 4 <> 0 then
Inc(BytesPerLine, 4 - BytesPerLine mod 4);
for y := 0 to Height - 1 do
begin
Line := pRgb;
for x := 0 to Width - 1 do
begin
Line[0] := RValue;
Line[1] := GValue;
Line[2] := BValue;
Inc(Line, 3);
end;
Inc(pRgb, BytesPerLine);
end;

Capturing bitmap fast eating RAM

This code is eating up my RAM at 1 to 2 percent a second (6 GB total).
Could anyone tell me what's wrong? Thanks in advance. I'm new to this, so if I sound like a complete idiot, I am. I'd like an answer fast.
#include <windows.h>
#include <iostream>
#include <stdio.h>
using namespace std;
/* Globals */
int ScreenX = 0;
int ScreenY = 0;
BYTE* ScreenData = 0;
void ScreenCap()
{
HDC hScreen = GetDC(GetDesktopWindow());
//hScreen2 = hScreen;
if (ScreenX == 0)
{
ScreenX = GetDeviceCaps(hScreen, HORZRES);
ScreenY = GetDeviceCaps(hScreen, VERTRES);
}
HDC hdcMem = CreateCompatibleDC (hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, ScreenX, ScreenY);
HGDIOBJ hOld = SelectObject(hdcMem, hBitmap);
BitBlt(hdcMem, 0, 0, ScreenX, ScreenY, hScreen, 0, 0, SRCCOPY);
SelectObject(hdcMem, hOld);
BITMAPINFOHEADER bmi = {0};
bmi.biSize = sizeof(BITMAPINFOHEADER);
bmi.biPlanes = 1;
bmi.biBitCount = 32;
bmi.biWidth = ScreenX;
bmi.biHeight = -ScreenY;
bmi.biCompression = BI_RGB;
bmi.biSizeImage = 0;// 3 * ScreenX * ScreenY;
if(ScreenData)
free(ScreenData);
ScreenData = (BYTE*)malloc(4 * ScreenX * ScreenY);
GetDIBits(hdcMem, hBitmap, 0, ScreenY, ScreenData, (BITMAPINFO*)&bmi, DIB_RGB_COLORS);
ReleaseDC(GetDesktopWindow(),hScreen);
DeleteDC(hdcMem);
}
inline int PosB(int x, int y)
{
return ScreenData[4*((y*ScreenX)+x)];
}
inline int PosG(int x, int y)
{
return ScreenData[4*((y*ScreenX)+x)+1];
}
inline int PosR(int x, int y)
{
return ScreenData[4*((y*ScreenX)+x)+2];
}
bool ButtonPress(int Key)
{
bool button_pressed = false;
while(GetAsyncKeyState(Key))
button_pressed = true;
return button_pressed;
}
int main()
{
while (true)
{
ScreenCap();
/*for (int x = 1; x < ScreenX; x++)
{
for (int y = 1; y < ScreenY; y++)
{
int Red = PosR(x, y);
int Green = PosG(x, y);
int Blue = PosB(x, y);
if (Red == 22 && Green == 58 && Blue == 89)
{
cout << ">:D";
POINT pos;
GetCursorPos(&pos);
int DX = 683 - x;
int DY = 683 - y;
/*COLORREF col = GetPixel(hScreen2, DX - pos.x + 1, pos.y - DY + 1);
int red = GetRValue(col);
int blue = GetBValue(col);
int green = GetGValue(col);
if (red == 22 && green == 58 && blue == 89)
{
break;
}
//SetCursorPos(x + DX, y + DY);
SetCursorPos(DX - pos.x + 1, pos.y - DY + 1);
cout << DX - pos.x << ", " << pos.y - DY + 1 << endl;
break;
}
}
}*/
}
system("PAUSE");
return 0;
}
You keep creating new bitmaps and never delete them.
Even better than freeing them each time would be to reuse the previous bitmap unless the screen size actually changed. Ditto for ScreenData. Reallocating unnecessarily is a performance killer.
You also aren't selecting the original object back in before destroying the DC, which is a problem.

Read pixels from 1bpp bitmap (CreateDIBSection)

This code works fine for 32, 24, 16, 8, 4 bpp images. But not for 1 bpp.
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
void GetWindowBitmapBitsAndSave(const HWND& hWnd, int nBitCount, char *szFilePath);
void EnumerateBits(LPBYTE pBits, int nBitCount, int nWidth, int nHeight);
int main(int argc, char **argv)
{
//GetWindowBitmapBits(HWND_DESKTOP, 32);
//32, 24, 16, 8, 4 works fine...
GetWindowBitmapBitsAndSave(HWND_DESKTOP, 1, "test.bmp");
return 0;
}
void EnumerateBits(LPBYTE pBits, int nBitCount, int nWidth, int nHeight)
{
int nPixels = nWidth * nHeight;
WORD wByteCount = nBitCount / 8;
if(wByteCount == 0)
wByteCount = 1;
int nBytes = nPixels * wByteCount;
for(int i = 0; i < nBytes; i += wByteCount)
{
int r = (int)(*(pBits+i));
//int g = (int)(*(pBits+i+1));
//int b = (int)(*(pBits+i+2));
//just for test
if(r != 0 /*&& g != 0 && b != 0*/)
if(r != 255 /*&& g != 255 && b != 255*/)
//printf("(%d, %d, %d)\n", r, g, b);
printf("%d\n", r);
}
}
void GetWindowBitmapBitsAndSave(const HWND& hWnd, int nBitCount, char* szFilePath)
{
HWND hScreenWnd = hWnd;
if(!hScreenWnd)
hScreenWnd = HWND_DESKTOP;
//calculate the number of color indexes in the color table
int nColorTableEntries = -1;
switch(nBitCount)
{
case 1:
nColorTableEntries = 2;
break;
case 4:
nColorTableEntries = 16;
break;
case 8:
nColorTableEntries = 256;
break;
case 16:
case 24:
case 32:
nColorTableEntries = 0;
break;
default:
nColorTableEntries = -1;
break;
}
if(nColorTableEntries == -1)
{
printf("bad bits-per-pixel argument\n");
return;
}
HDC hDC = GetDC(hScreenWnd);
HDC hMemDC = CreateCompatibleDC(hDC);
int nWidth = 0;
int nHeight = 0;
if(hScreenWnd != HWND_DESKTOP)
{
RECT rect;
GetClientRect(hScreenWnd, &rect);
nWidth = rect.right - rect.left;
nHeight = rect.bottom - rect.top;
}
else
{
nWidth = ::GetSystemMetrics(SM_CXSCREEN);
nHeight = ::GetSystemMetrics(SM_CYSCREEN);
}
HBITMAP hBMP = CreateCompatibleBitmap(hDC, nWidth, nHeight);
SelectObject(hMemDC, hBMP);
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, 0, 0, SRCCOPY);
int nStructLength = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
BITMAPINFOHEADER BitmapInfoHeader;
BitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
BitmapInfoHeader.biWidth = nWidth;
BitmapInfoHeader.biHeight = nHeight;
BitmapInfoHeader.biPlanes = 1;
BitmapInfoHeader.biBitCount = nBitCount;
BitmapInfoHeader.biCompression = BI_RGB;
BitmapInfoHeader.biXPelsPerMeter = 0;
BitmapInfoHeader.biYPelsPerMeter = 0;
BitmapInfoHeader.biClrUsed = nColorTableEntries;
BitmapInfoHeader.biClrImportant = nColorTableEntries;
DWORD dwBytes = ((DWORD) nWidth * nBitCount) / 32;
if(((DWORD) nWidth * nBitCount) % 32) {
dwBytes++;
}
dwBytes *= 4;
DWORD dwSizeImage = dwBytes * nHeight;
BitmapInfoHeader.biSizeImage = dwSizeImage;
BITMAPINFO* bitmapInfo = new BITMAPINFO[sizeof(BITMAPINFO) + sizeof(RGBQUAD) * nColorTableEntries];
bitmapInfo->bmiHeader = BitmapInfoHeader;
if(nBitCount < 16)
{
for(int i = 0; i != nColorTableEntries; ++i)
{
bitmapInfo->bmiColors[i].rgbRed =
bitmapInfo->bmiColors[i].rgbGreen =
bitmapInfo->bmiColors[i].rgbBlue = (BYTE)(i*(255/(nColorTableEntries-1)));
bitmapInfo->bmiColors[i].rgbReserved = 0;
}
}
LPBYTE lpDibBits = 0;
HBITMAP hBitmap = ::CreateDIBSection(hMemDC, bitmapInfo, DIB_RGB_COLORS, (void**)&lpDibBits, NULL, 0);
SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, 0, 0, SRCCOPY);
ReleaseDC(hScreenWnd, hDC);
//save bitmap
BITMAPFILEHEADER bmfh;
bmfh.bfType = 0x4d42; // 'BM'
int nHeaderSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
bmfh.bfSize = 0;
bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
FILE *pFile = 0;
fopen_s(&pFile, szFilePath, "wb");
if(pFile)
{
fwrite(&bmfh, sizeof(BITMAPFILEHEADER), 1, pFile);
fwrite(bitmapInfo, nHeaderSize,1,pFile);
fwrite(lpDibBits, dwSizeImage, 1, pFile);
fclose(pFile);
}
EnumerateBits(lpDibBits, nBitCount, nWidth, nHeight);
delete[]bitmapInfo;
::DeleteObject(hBMP);
::DeleteObject(hBitmap);
}
First question: Why when bit count is 1 there are colors other than black and white? (but image is monochrome. Just comment EnumerateBits call in GetWindowBitmapBitsAndSave function).
Second question: How correctly calculate offset for color bits array?