Fastest RGB color detection C++ - c++

Hi i have used getpixel method, bitblt or what its called (creating bitmap header) and then going though all the values. It is VERY slow. For exapmple if i had to detect something red or specific color it would take very long time. There has gotta be faster way right? I did try using desktop as HWND and then the window i need to find the colors, but desktop was somehow faster.. guesing because it had to look for the window i guess. I get HIGH cpu usage using both methods.
void Get_Color(int x,int y,int w,int h,int &red,int &green,int &blue,int action)
{
HDC hdc, hdcTemp;
RECT rect;
BYTE*bitPointer;
HWND Desktop = GetDesktopWindow();
hdc = GetDC(Desktop);
GetWindowRect(Desktop, &rect);
hdcTemp = CreateCompatibleDC(hdc);
BITMAPINFO bitmap;
bitmap.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmap.bmiHeader.biWidth = w;
bitmap.bmiHeader.biHeight = h;
bitmap.bmiHeader.biPlanes = 1;
bitmap.bmiHeader.biBitCount = 32;
bitmap.bmiHeader.biCompression = BI_RGB;
bitmap.bmiHeader.biSizeImage = 0;
bitmap.bmiHeader.biClrUsed = 0;
bitmap.bmiHeader.biClrImportant = 0;
HBITMAP hBitmap2 = CreateDIBSection(hdcTemp, &bitmap, DIB_RGB_COLORS, (void**)(&bitPointer), NULL, NULL);
HGDIOBJ save = SelectObject(hdcTemp, hBitmap2);
BitBlt(hdcTemp, 0, 0, w, h, hdc, x, y, SRCCOPY);
if(action==1)
{
for(int j=0;j<=w*h*4;j+=4)
{
red = bitPointer[j+2];
green = bitPointer[j+1];
blue = bitPointer[j];
if(red<30 && green>190 && blue>190)
{
break;
}
}
}
else
{
for(int j=0;j<=w*h*4;j+=4)
{
red = bitPointer[j+2];
green = bitPointer[j+1];
blue = bitPointer[j];
break;
}
}
///RELEASE
DeleteObject( SelectObject(hdcTemp, save) );
DeleteDC(hdcTemp);
DeleteDC(hdc);
ReleaseDC(NULL,hdc);
ReleaseDC(NULL,hdcTemp);
}

You may try to break your search. First search in the red channel and, when this succeed look for blue and green values:
for (int j=0; j<=w*h*4; j+=4){
red = bitPointer[j+2];
if (red<30) {
green = bitPointer[j+1];
blue = bitPointer[j];
if (green>190 && blue>190) {
do_something;
}
}
}
You can also try to accelerate via pointer arithmetic (but a good compiler will easily optimize the former):
for (BYTE *pRed=bitPointer+2; pRed<=bitPointer+w*h*4; pRed+=4){
if (pRed<30) {
green = pRed[-1];
blue = pRed[-2];
if (green>190 && blue>190) {
do_something;
}
}
}
If this is not sufficient, you may think about using threads to break the search into separate parallels smaller searches.

I suggest you create a table of RGB values from the bitmap. This would only need to be performed once.
struct RGB
{
unsigned int red;
unsigned int green;
unsigned int blue;
};
RGB rgb_pixels[MAX_ROWS][MAX_COLUMNS];
// ...
for (unsigned int row = 0; row < MAX_ROWS; ++row)
{
for (unsigned int column = 0; column < MAX_COLUMNS; ++column)
{
unsigned red = get_red_value(row, column);
unsigned green = get_green_value(row, column);
unsigned blue = get_blue_value(row, column);
RGB pixel;
pixel.red = red;
pixel.green = green;
pixel.blue = blue;
rgb_pixels[row][column] = pixel;
}
}
Whenever your program needs RGB info from the bitmap, index the array (rgb_pixels).
Accessing the RGB pixel array will be much faster than searching the bitmap and converting, for each pixel you are interested in.

Basically every time you call your method, it is creating an entirely new bitmap and copying it again. You could speed up your program the most by keeping the array of colors between calls to Get_Color(). You could put it in a class or a global variable. Thomas Matthews has a good idea for that. Once that's fixed, Jean-Baptiste Yunès has some good advice to look into.

Related

How to extract bitmap from spritesheet in Win32 C++?

I'm trying to load individual cards from a spritesheet of cards based on suit and rank but I'm unsure of how to construct a new Bitmap object from cutting out Rectangle coordinates in the source image. I'm using <windows.h> currently and trying to find a simple way to accomplish this. I'm looking for something like this:
HBITMAP* twoOfHearts = CutOutFromImage(sourceImage, new Rectangle(0, 0, 76, 116));
From source: http://i.stack.imgur.com/WZ9Od.gif
Here's a function I played with the other week for more-or-less this same task. In my case, I wanted to return a HBRUSH that could be used with FillRect. In that instance, we still need to create a bitmap of the area of interest, before then going on to create a brush from it.
In your case, just return the dstBmp instead. spriteSheet is a global that has had a 256x256 spritesheet loaded. I've hardcoded the size of my sprites to 16x16, you'd need to change that to something like 81x117.
Here's some code that grabs a copy of the required area and some more that uses these 'stamps' to draw a level map. That said - there are all kinds of problems with this approach. Speed is one, excessive work is another one that impacts on the first. Finally, scrolling a window drawn like this produces artefacts.
// grabs a 16x16px section from the spriteSheet HBITMAP
HBRUSH getSpriteBrush(int col, int row)
{
HDC memDC, dstDC, curDC;
HBITMAP oldMemBmp, oldDstBmp, dstBmp;
curDC = GetDC(HWND_DESKTOP);
memDC = CreateCompatibleDC(NULL);
dstDC = CreateCompatibleDC(NULL);
dstBmp = CreateCompatibleBitmap(curDC, 16, 16);
int xOfs, yOfs;
xOfs = 16 * col;
yOfs = 16 * row;
oldMemBmp = (HBITMAP)SelectObject(memDC, spriteSheet);
oldDstBmp = (HBITMAP)SelectObject(dstDC, dstBmp);
BitBlt(dstDC,0,0,16,16, memDC, xOfs,yOfs, SRCCOPY);
SelectObject(memDC, oldMemBmp);
SelectObject(dstDC, oldDstBmp);
ReleaseDC(HWND_DESKTOP, curDC);
DeleteDC(memDC);
DeleteDC(dstDC);
HBRUSH result;
result = CreatePatternBrush(dstBmp);
DeleteObject(dstBmp);
return result;
}
void drawCompoundSprite(int x, int y, HDC paintDC, char *tileIndexes, int numCols, int numRows)
{
int mapCol, mapRow;
HBRUSH curSprite;
RECT curDstRect;
for (mapRow=0; mapRow<numRows; mapRow++)
{
for (mapCol=0; mapCol<numCols; mapCol++)
{
int curSpriteIndex = tileIndexes[mapRow*numCols + mapCol];
int spriteX, spriteY;
spriteX = curSpriteIndex % 16;
spriteY = curSpriteIndex / 16;
curDstRect.left = x + 16*mapCol;
curDstRect.top = y + 16 * mapRow;
curDstRect.right = curDstRect.left + 16;
curDstRect.bottom = curDstRect.top + 16;
curSprite = getSpriteBrush(spriteX, spriteY);
FillRect(paintDC, &curDstRect, curSprite);
DeleteObject(curSprite);
}
}
}
The latter function has since been replaced with the following:
void drawCompoundSpriteFast(int x, int y, HDC paintDC, unsigned char *tileIndexes, int numCols, int numRows, pMapInternalData mData)
{
int mapCol, mapRow;
HBRUSH curSprite;
RECT curDstRect;
HDC memDC;
HBITMAP oldBmp;
memDC = CreateCompatibleDC(NULL);
oldBmp = (HBITMAP)SelectObject(memDC, mData->spriteSheet);
for (mapRow=0; mapRow<numRows; mapRow++)
{
for (mapCol=0; mapCol<numCols; mapCol++)
{
int curSpriteIndex = tileIndexes[mapRow*numCols + mapCol];
int spriteX, spriteY;
spriteX = curSpriteIndex % mData->tileWidth;
spriteY = curSpriteIndex / mData->tileHeight
// Draw sprite as-is
// BitBlt(paintDC, x+16*mapCol, y+16*mapRow,
// mData->tileWidth, mData->tileHeight,
// memDC,
// spriteX * 16, spriteY*16,
// SRCCOPY);
// Draw sprite with magenta rgb(255,0,255) areas treated as transparent (empty)
TransparentBlt(
paintDC,
x+mData->tileWidth*mapCol, y+mData->tileHeight*mapRow,
mData->tileWidth, mData->tileHeight,
memDC,
spriteX * mData->tileWidth, spriteY*mData->tileHeight,
mData->tileWidth, mData->tileHeight,
RGB(255,0,255)
);
}
}
SelectObject(memDC, oldBmp);
DeleteObject(memDC);
}

Analyze Screenshots with c++ and GDI to get average color of screen

I am working on a project that I am trying to design for a class. I want to try and mimic the behavior of Phillips' Abilight or the common homebrew 'BobLight'. It gets the average color of the screen,via screenshot, in my case by averaging the RGB pixel data, pixel by pixel. I have decided to try and do this by using the windows GDI api and so far I am getting some pretty good results, I learned not to use GetPixel() to read pixel data since its so incredibly slow, and my speed is perfectly acceptable so far.
My problem is that when I try to implement it in a loop that can run as long as desired, it eventually hangs giving me an error saying: std::bad_alloc at memory location 0x0027F9C4.
at this line of code:
BYTE *lpbitmap = new BYTE[dwBmpSize];
I attached my entire code below
Does anyone have an idea what could be causing this error? Am I shouldn't be running out of any kind of memory, and I'm really at a loss..
Thanks in advance for any help!
#define _WIN32_WINNT 0x0501 //xp
#include <windows.h>
#include <iostream>
#include <vector>
#include <time.h>
#define NUM_FRAMES 180
using namespace std;
int main()
{
int time_start, time_finish;
float time_total;
time_start = clock();
for(int z = 0; z < NUM_FRAMES; z++)
{
HDC hScreenDC = CreateDC(TEXT("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);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, x, y);
// get a new bitmap
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
RECT desktop;
// Get a handle to the desktop window
const HWND hDesktop = GetDesktopWindow();
// Get the size of screen to the variable desktop
GetWindowRect(hDesktop, &desktop);
// The top left corner will have coordinates (0,0)
// and the bottom right corner will have coordinates
// (horizontal, vertical)
int horizontal = desktop.right;
int vertical = desktop.bottom;
BitBlt(hMemoryDC, 0, 0, horizontal, vertical, hScreenDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = horizontal;
bi.biHeight = vertical;
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 = ((horizontal * bi.biBitCount + 31) / 32) * 4 * vertical;
// Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that
// call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc
// have greater overhead than HeapAlloc.
HANDLE hDIB = GlobalAlloc(GHND,dwBmpSize);
BYTE *lpbitmap = new BYTE[dwBmpSize];
//BITMAP bm;
GetDIBits(hMemoryDC,hBitmap,0,(UINT)vertical,lpbitmap, (BITMAPINFO *)&bi, DIB_RGB_COLORS);
GetObject (hBitmap, sizeof(lpbitmap), &lpbitmap);
unsigned long red = 0, green = 0, blue = 0;
for (int i = 0; i < horizontal; i++)
{
for (int j = 0; j < vertical; j++)
{
blue += lpbitmap[0];
green += lpbitmap[1];
red += lpbitmap[2];
lpbitmap += 4;
}
}
red = red/(horizontal*vertical);
green = green/(horizontal*vertical);
blue = blue/(horizontal*vertical);
//cout << "Red: " << red << " Green: " << green << " Blue: " << blue << endl;
}
time_finish = clock();
time_total = time_finish - time_start;
cout << endl << NUM_FRAMES/((time_total)/10000) << endl;
//release
//DeleteDC(hdc);
//DeleteObject(hbmp);
//ReleaseDC(NULL, hdcScreen);
std::system("pause");
return 0;
}
Your release code appears to be commented out, meaning you're at least leaking hBitmap every frame, which will add up quickly (around 470MB/s to be specific). Actually, it looks like you're leaking lpbitmap as well, so your 180 frames will burn about 2.8 GB on a 1080p display.

c/c++ assign RGBQUAD array to a bitmap

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

How to display OpenCV Mat on MFC View

I thought displaying OpenCV2 Mat on MFC View is simple but is not. This is only relevant material I found on google. Excuse me for my ignorance but I can't find any other materials showing how to use SetDIBitsToDevice with one dimensional array "data" member returns. More specifically, I need to know how to specify BITMAPINFO for the function. Do I go back to Old C-style OpenCV to work with MFC?
UPDATE:
I found an example of SetDIBitsToDevice which is actually for old C-style OpenCV. But it was straightforward to convert it for OpenCV2. There are things I need to mention to make it work:
Bpp method does not work well as Mat's depth returns 0. I just changed like this:
static int Bpp(cv::Mat img) { return 8 * img.channels(); }
Mat does not have origin member. But simply putting 0 is fine for origin argument of FillBitmapInfo method.
Other than that, following code works great. Hope this helps other devs too.
void COpenCVTestView::OnDraw(CDC* pDC)
{
COpenCVTestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
if(pDoc->m_cvImage.empty()) return;
// TODO: add draw code for native data here
int height=pDoc->m_cvImage.rows;
int width=pDoc->m_cvImage.cols;
uchar buffer[sizeof( BITMAPINFOHEADER ) + 1024];
BITMAPINFO* bmi = (BITMAPINFO* )buffer;
FillBitmapInfo(bmi,width,height,Bpp(pDoc->m_cvImage),0);
SetDIBitsToDevice(pDC->GetSafeHdc(), 0, 0, width,
height, 0, 0, 0, height, pDoc->m_cvImage.data, bmi,
DIB_RGB_COLORS);
}
void COpenCVTestView::FillBitmapInfo(BITMAPINFO* bmi, int width, int height, int bpp, int origin)
{
assert(bmi && width >= 0 && height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32));
BITMAPINFOHEADER* bmih = &(bmi->bmiHeader);
memset(bmih, 0, sizeof(*bmih));
bmih->biSize = sizeof(BITMAPINFOHEADER);
bmih->biWidth = width;
bmih->biHeight = origin ? abs(height) : -abs(height);
bmih->biPlanes = 1;
bmih->biBitCount = (unsigned short)bpp;
bmih->biCompression = BI_RGB;
if (bpp == 8)
{
RGBQUAD* palette = bmi->bmiColors;
for (int i = 0; i < 256; i++)
{
palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
palette[i].rgbReserved = 0;
}
}
}
Here is another possible way of displaying OpenCV data in MFC which I use and works great:
IplImage* image// <-- this contains the image you want to display
CvvImage tempdefault;
RECT myrect; // <-- specifiy where on the screen you want it to be displayed
myrect.top = 0;
myrect.bottom = _pictureh;
myrect.left = _picturex;
myrect.right = _picturew+_picturex;
tempdefault.Create(_pictureh,_picturew,32);
tempdefault.CopyOf(image);
tempdefault.DrawToHDC(pDC->GetSafeHdc(),&myrect);
From MSDN:
lpvBits [in]
A pointer to the color data stored as an array of bytes. For more information, see the following Remarks section.
This is the pointer you must init with the data returned from Mat::data.
CvvImage is not available in new versions of OpenCV. Using the following code you can convert Mat to CImage and then display CImage everywhere you want:
int Mat2CImage(Mat *mat, CImage &img){
if(!mat || mat->empty())
return -1;
int nBPP = mat->channels()*8;
img.Create(mat->cols, mat->rows, nBPP);
if(nBPP == 8)
{
static RGBQUAD pRGB[256];
for (int i = 0; i < 256; i++)
pRGB[i].rgbBlue = pRGB[i].rgbGreen = pRGB[i].rgbRed = i;
img.SetColorTable(0, 256, pRGB);
}
uchar* psrc = mat->data;
uchar* pdst = (uchar*) img.GetBits();
int imgPitch = img.GetPitch();
for(int y = 0; y < mat->rows; y++)
{
memcpy(pdst, psrc, mat->cols*mat->channels());//mat->step is incorrect for those images created by roi (sub-images!)
psrc += mat->step;
pdst += imgPitch;
}
return 0;
}

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.