Get Pixel color fastest way? - c++

I'm trying to make an auto-cliker for an windows app. It works well, but it's incredibly slow!
I'm currently using the method "getPixel" which reloads an array everytime it's called.
Here is my current code:
hdc = GetDC(HWND_DESKTOP);
bx = GetSystemMetrics(SM_CXSCREEN);
by = GetSystemMetrics(SM_CYSCREEN);
start_bx = (bx/2) - (MAX_WIDTH/2);
start_by = (by/2) - (MAX_HEIGHT/2);
end_bx = (bx/2) + (MAX_WIDTH/2);
end_by = (by/2) + (MAX_HEIGHT/2);
for(y=start_by; y<end_by; y+=10)
{
for(x=start_bx; x<end_bx; x+=10)
{
pixel = GetPixel(*hdc, x, y);
if(pixel==RGB(255, 0, 0))
{
SetCursorPos(x,y);
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Sleep(50);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
Sleep(25);
}
}
}
So basically, it just scan a range of pixel in the screen and starts a mouse event if it detects a red button.
I know there are other ways to get the pixel color, such as bitblt. But I've made some researches, and I don't understand how I'm supposed to do, in order to scan a color array. I need something which scans screen very fast in order to catch the button.
Could you please help me?
Thanks.

I found a perfect way which is clearly faster than the GetPixel one:
HDC hdc, hdcTemp;
RECT rect;
BYTE* bitPointer;
int x, y;
int red, green, blue, alpha;
while(true)
{
hdc = GetDC(HWND_DESKTOP);
GetWindowRect(hWND_Desktop, &rect);
int MAX_WIDTH = rect.right;
int MAX_HEIGHT = rect.bottom;
hdcTemp = CreateCompatibleDC(hdc);
BITMAPINFO bitmap;
bitmap.bmiHeader.biSize = sizeof(bitmap.bmiHeader);
bitmap.bmiHeader.biWidth = MAX_WIDTH;
bitmap.bmiHeader.biHeight = MAX_HEIGHT;
bitmap.bmiHeader.biPlanes = 1;
bitmap.bmiHeader.biBitCount = 32;
bitmap.bmiHeader.biCompression = BI_RGB;
bitmap.bmiHeader.biSizeImage = MAX_WIDTH * 4 * MAX_HEIGHT;
bitmap.bmiHeader.biClrUsed = 0;
bitmap.bmiHeader.biClrImportant = 0;
HBITMAP hBitmap2 = CreateDIBSection(hdcTemp, &bitmap, DIB_RGB_COLORS, (void**)(&bitPointer), NULL, NULL);
SelectObject(hdcTemp, hBitmap2);
BitBlt(hdcTemp, 0, 0, MAX_WIDTH, MAX_HEIGHT, hdc, 0, 0, SRCCOPY);
for (int i=0; i<(MAX_WIDTH * 4 * MAX_HEIGHT); i+=4)
{
red = (int)bitPointer[i];
green = (int)bitPointer[i+1];
blue = (int)bitPointer[i+2];
alpha = (int)bitPointer[i+3];
x = i / (4 * MAX_HEIGHT);
y = i / (4 * MAX_WIDTH);
if (red == 255 && green == 0 && blue == 0)
{
SetCursorPos(x,y);
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Sleep(50);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
Sleep(25);
}
}
}
I hope this could help someone else.

The simple answer is that if this is the method you insist on using then there isn't much to optimize. As others have pointed out in comments, you should probably use a different method for locating the area to click. Have a look at using FindWindow, for example.
If you don't want to change your method, then at least sleep your thread for a bit after each complete screen scan.

Related

Capture pixel data from only a specified window

I want the code below to take a screenshot of a specified window only, becuse this way BitBlt is faster.
The code below takes a screenshot of a window, specified by the name of window, loads the pixel data in a buffer and then re-draws the picture on your screen just to prove the copying worked.
I'd like to take screenshots of browser windows like Google Chrome, but it doesn't seem to work. It draws a black rectangle on my screen with the correct dimensions of the window.
Seems to be working all the time with the window of Minecraft.
Also worked with the Tor Browser.
I noticed that after querying window info, minecraft's window had no child-windows, but when it didn't work with the others, all of them had multiple child-windows.
#include<iostream>
#include<Windows.h>
#include<vector>
using namespace std;
int main() {
HWND wnd = FindWindow(NULL, "Minecraft 1.15.1");//Name of window to be screenshoted
if (!wnd) {
std::cout << "e1\n";
std::cin.get();
return 0;
}
WINDOWINFO wi = { 0 };
wi.cbSize = sizeof(WINDOWINFO);
GetWindowInfo(wnd, &wi);
RECT rect;
GetWindowRect(wnd, &rect);
int width = wi.rcClient.right - wi.rcClient.left;
int height = wi.rcClient.bottom - wi.rcClient.top;
cout << width << height;
/////Fetch window info^^^^^^^^
BYTE* ScreenData = new BYTE[4 * width*height];
// copy screen to bitmap
HDC hScreen = GetWindowDC(wnd);
HDC hDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, width, height);
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
BitBlt(hDC, 0, 0, width, height, hScreen, 0, 0, SRCCOPY);
SelectObject(hDC, old_obj);
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
std::cout << GetDIBits(hDC, hBitmap, 0, 0, NULL, &bmi, DIB_RGB_COLORS)<<endl;
//std::cout << bmi.bmiHeader.biHeight<< " "<< bmi.bmiHeader.biWidth<<endl;
BYTE*buffer = new BYTE[4* bmi.bmiHeader.biHeight*bmi.bmiHeader.biWidth];
bmi.bmiHeader.biHeight *= -1;
std::cout<<GetDIBits(hDC, hBitmap, 0, bmi.bmiHeader.biHeight, buffer, &bmi, DIB_RGB_COLORS)<<endl;//DIB_PAL_COLORS
/////Load window pixels into a buffer^^^^^^^^
POINT p;
int r = 0;
int g = 0;
int b = 0;
int x = 0;
int y = 0;
HDC sc = GetDC(NULL);
COLORREF color;
while (true) {
if ((y*-1) == bmi.bmiHeader.biHeight+1 && x == bmi.bmiHeader.biWidth-1) { break; }
r = (int)buffer[4 * ((y*bmi.bmiHeader.biWidth) + x)+2];
g = (int)buffer[4 * ((y*bmi.bmiHeader.biWidth) + x)+1];
b = (int)buffer[4 * ((y*bmi.bmiHeader.biWidth) + x) ];
color = RGB(r, g, b);
SetPixel(sc, x, y, color);
x++;
if (x == bmi.bmiHeader.biWidth) {
y++;
x = 0;
}
}
/////Prove that the copying was successful and buffer is full^^^^^^^^
Sleep(5000);
std::cout << "fin\n";
std::cin.get();
return 0;
}
I think the problem is the order you're doing things.
Before you put the bitmap on the clipboard, you should select it out of your memory device context.
Once you put the bitmaps on the clipboard, you no longer own it, so you shouldn't try to delete it.
The best way to do that is probably to move your // clean up section before the // save bitmap to clipboard section and to eliminate your DelectObject(hBitmap) statement. So the tail end of your code should probably be:
BitBlt(hDC, 0, 0, width, height, hScreen, 0, 0, SRCCOPY);
// clean up
SelectObject(hDC, old_obj); // selects hBitmap out of hDC
DeleteDC(hDC);
ReleaseDC(NULL, hScreen);
// save bitmap to clipboard
OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap); // clipboard now owns the bitmap
CloseClipboard();
If you still have a problem after those changes, I would check the return value of the SetClipboardData call. If that's failing, GetLastError may give a clue.

GetPixel() not working correctly Windows API C++

I'm writing a program that reads each pixel of a window and store it in an array of bytes as black and white, each bit of the bytes is a black/white value.
But GetPixel() doesn't seem to work the way I expected. Here's the part of the code for reading pixels and storing them:
byte *colors = new byte[250000 / 8 + 1];
ZeroMemory(colors, 250000 / 8 + 1);
HDC hdc = GetDC(hwnd);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP memBitmap = CreateCompatibleBitmap(hdc, 500, 500);
SelectObject(memDC, memBitmap);
BitBlt(memDC, 0, 0, 500, 500, hdc, 0, 0, SRCCOPY);
for (int y = 0; y < 500; y++) {
for (int x = 0; x < 500; x++) {
COLORREF pxcolor = GetPixel(memDC, x, y);
if (pxcolor == CLR_INVALID) {
MessageBox(hwnd, _T("Oops..."), NULL, NULL);
}
int r = GetRValue(pxcolor);
int g = GetGValue(pxcolor);
int b = GetBValue(pxcolor);
int average = (r + g + b) / 3;
bool colorBW = average >= 128;
int currentIndex = y * 500 + x;
if (colorBW) {
SetBit(colors, currentIndex);
}
}
}
ReleaseDC(hwnd, hdc);
DeleteDC(memDC);
DeleteObject(memBitmap);
delete[] colors;
SetBit():
inline VOID SetBit(byte *bytes, int index, bool state = true) {
byte byteToSet = bytes[index / 8];
int bitNumber = index % 8;
bytes[index / 8] = state ? (byteToSet | (0b1000'0000 >> bitNumber)) : (byteToSet & ((0b1111'1111 >> (bitNumber + 1)) | (0b1111'1111 << (8 - bitNumber - 1))));
}
Every pixel read in by GetPixel() seems to give me 0x000000, or pure black.
My code used to call GetPixel() with the first parameter being hdc, without all the bitmap and memory DC stuff, but that way every pixel returns CLR_INVALID. I came across this question, and the above code is after I have changed it into using memory DCs and bitmaps. But it just went from returning CLR_INVALID to 0x000000 for each pixel.
If I add this line before I use GetPixel():
SetPixel(memDC, x, y, RGB(255, 255, 255));
GetPixel() returns the correct result. Why is it functioning this way?

Given just a HBITMAP, how to draw to it?

I'm an absolute beginner at this but have managed to blunder my way to 93% of where I want to be. Need help for the final 7%.
I've manually created a bitmap like so:
BITMAPINFO bmpInfo = { 0 };
BITMAPINFOHEADER bmpInfoHeader = { 0 };
BITMAP ImageBitmap;
void *bits;
bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfoHeader.biBitCount = 32;
bmpInfoHeader.biClrImportant = 0;
bmpInfoHeader.biClrUsed = 0;
bmpInfoHeader.biCompression = BI_RGB;
bmpInfoHeader.biHeight = -IMAGE_DISPLAY_HEIGHT;
bmpInfoHeader.biWidth = IMAGE_DISPLAY_WIDTH;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biSizeImage = IMAGE_DISPLAY_WIDTH * IMAGE_DISPLAY_HEIGHT * 4;
ZeroMemory(&bmpInfo, sizeof(bmpInfo));
bmpInfo.bmiHeader = bmpInfoHeader;
bmpInfo.bmiColors->rgbBlue = 0;
bmpInfo.bmiColors->rgbGreen = 0;
bmpInfo.bmiColors->rgbRed = 0;
bmpInfo.bmiColors->rgbReserved = 0;
g_hImageBitmap = CreateDIBSection(hDC, &bmpInfo, DIB_RGB_COLORS, &bits, NULL, 0);
GetObject(g_hImageBitmap, sizeof(BITMAP), &ImageBitmap);
for (i = 0; i < IMAGE_DISPLAY_WIDTH; i++) {
for (j = 0; j < IMAGE_DISPLAY_HEIGHT; j++) {
((unsigned char *)bits)[j*IMAGE_DISPLAY_WIDTH * 4 + i * 4] = 255; // Blue
((unsigned char *)bits)[j*IMAGE_DISPLAY_WIDTH * 4 + i * 4 + 1] = 255; // Green
((unsigned char *)bits)[j*IMAGE_DISPLAY_WIDTH * 4 + i * 4 + 2] = 255; // Red
((unsigned char *)bits)[j*IMAGE_DISPLAY_WIDTH * 4 + i * 4 + 3] = 0;
}
}
g_ImageBitmapPixels = bits;
and elsewhere WM_PAINT handles drawing this like so
hdc = BeginPaint(hwnd, &ps);
if (g_hImageBitmap != NULL) {
GetObject(g_hImageBitmap, sizeof(BITMAP), &bm);
hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, g_hImageBitmap);
BitBlt(hdc, UPPER_LEFT_IMAGE_X, UPPER_LEFT_IMAGE_Y,
bm.bmWidth, bm.bmHeight, hMemoryDC, 0, 0, SRCCOPY);
SelectObject(hMemoryDC, hOldBitmap);
}
Given the global variable g_ImageBitmapPixels other parts of the program can change and manipulate individual pixels in the bitmap, and when that happens, I use
InvalidateRect(hwnd, &RECT_ImageUpdate_Window, TRUE);
UpdateWindow(hwnd);
to update just that little portion of the screen. Works great. Hooray for me.
To get to the point, my question is, if a function has ONLY the HBITMAP (g_hImageBitmap) ... is there a way to call the Windows library functions to draw lines, text, circles, filled circles, to the HBITMAP? Like these functions
MoveToEx(hDC, x1, y1, NULL);
LineTo(hDC, x2, y2 );
HBRUSH hRedBrush = CreateSolidBrush(RGB(255, 0, 0));
FillRect(hDC, &somerectangle, hRedBrush);
except instead of needing a device context, they just take the HBITMAP?
I have a pointer to the actual pixels (g_ImageBitmapPixels) so I could just write my own line drawing, circle drawing, rectangle filling functions. Indeed I have done that, but it seems a shame not to use the functions Microsoft so kindly provides. Also, I'm not smart enough to make my own text-drawing functions.
Thank you for your help.

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

Wrong colors when using StretchDIBits

I have got a trouble using the StretchDIBits function.
I want to draw a bitmap made from a buffer. However, the colors I define in the buffer are different from the result on screen.
I have read the documentation and I played with the biCompression (BI_RGB and BI_BITFIELDS) and biClrUsed (0 / 3) parameters of the BITMAPINFOHEADER. I can see some differences depending on their values, but the result is still different from what I am expecting.
Here is the code I am using (it can be inserted in the OnDraw method of a template SDI project to demonstrate the problem).
void CTestStretchDIBitsView::OnDraw(CDC* /*pDC*/)
{
...
CClientDC dc(this);
CRect rect;
GetClientRect(&rect);
DWORD* pBuffer = new DWORD[500 * 500];
memset(pBuffer, RGB(255, 255, 0), 500 * 500 * sizeof(DWORD));
LPBITMAPINFO pBmpInfo = (LPBITMAPINFO) new BYTE[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
pBmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pBmpInfo->bmiHeader.biWidth = 500;
pBmpInfo->bmiHeader.biHeight = 500;
pBmpInfo->bmiHeader.biPlanes = 1;
pBmpInfo->bmiHeader.biBitCount = 32;
pBmpInfo->bmiHeader.biCompression = BI_BITFIELDS;
pBmpInfo->bmiHeader.biSizeImage = 500 * 500;
pBmpInfo->bmiHeader.biXPelsPerMeter = 0;
pBmpInfo->bmiHeader.biYPelsPerMeter = 0;
pBmpInfo->bmiHeader.biClrUsed = 0;
pBmpInfo->bmiHeader.biClrImportant = 0;
SetStretchBltMode(dc.m_hDC, STRETCH_DELETESCANS);
StretchDIBits(dc.m_hDC,
0,
rect.Height(),
rect.Width(),
-rect.Height(),
0,
0,
500,
500,
pBuffer,
pBmpInfo,
DIB_RGB_COLORS,
SRCCOPY);
delete[] pBmpInfo;
delete[] pBuffer;
}
You have to use the following mode
SetStretchBltMode(hdcWindow,HALFTONE);
instead of
SetStretchBltMode(dc.m_hDC, STRETCH_DELETESCANS);
because halftone is the best mode according to my research.
The problem didn't come from the StretchDIBits function but from the initialization of the buffer used as the bitmap here.
memset(...) function was misused.
With an initialization such as :
int Color = RGB(255, 0, 0);
for (int i = 0 ; i < 500 * 500 ; i++)
pBuffer[i] = Color;
I get a perfectly blue image as expected.