3

I simply want to scan a bitmap I have in memory for a specific color given the RGB values of a given pixel, and if found, give me the x, y cords of that pixel. I'm have code here that stores the bitmap of the given window in memory, and works fine. But when I try to retrieve where the pixel is with a red value of 60, i get all kinds of funky values. Here is my current code:

bool findColor(int x, int y, int w, int h, LPCSTR fname) {
HWND window = FindWindow(0, ("windownamehere"));
HDC hdcSource = GetDC(window);
HDC hdcMemory = CreateCompatibleDC(hdcSource);
int capX = GetDeviceCaps(hdcSource, HORZRES);
int capY = GetDeviceCaps(hdcSource, VERTRES);
HBITMAP hBitmap = CreateCompatibleBitmap(hdcSource, w, h);
HBITMAP hBitmapOld = (HBITMAP)SelectObject(hdcMemory, hBitmap);
BitBlt(hdcMemory, 0, 0, w, h, hdcSource, x, y, SRCCOPY);
SelectObject(hdcMemory, hBitmapOld);
//Added
HDC hdc = GetDC(0);
BITMAPINFO MyBMInfo = { 0 };
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
GetDIBits(hdcMemory, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS);
BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];
MyBMInfo.bmiHeader.biCompression = BI_RGB;
GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS);
BYTE red, blue, green;
char* pCurrPixel = (char*)lpPixels;
for (y = 0; y < h; y++)
{
    for (x = 0; x < w; x++)
    {
        red = pCurrPixel[0];
        green = pCurrPixel[1];
        blue = pCurrPixel[2];
        if ((red == 134))
            std::cout << x << ", " << y;
        pCurrPixel += 4;
    }
}
SelectObject(hdcMemory, hBitmapOld);
DeleteObject(hBitmap);
DeleteDC(hdcSource);
DeleteDC(hdcMemory);
return false;
}
  • Duplicate? https://stackoverflow.com/questions/10515646/get-pixel-color-fastest-way – Sharad Khanna Mar 28 '18 at 19:49
  • Using the following code from this post: did not work correctly. I copy and pasted as is and used it with: https://www.w3schools.com/colors/colors_converter.asp And It returned 0 results with multiple tests. – Billy Penley Mar 28 '18 at 21:20

1 Answers1

2

hBitmap = (HBITMAP)SelectObject(hdcMemory, hBitmapOld);

That's going to overwrite hBitmap, you don't want that. You should change that line to:

SelectObject(hdcMemory, hBitmapOld);

Then you can use GetDIBits

Also you have already copied the bitmap to memory dc. You can use GetPixel to obtain the color. There is no need to call GetDIBits

HBITMAP hBitmapOld = (HBITMAP)SelectObject(hdcMemory, hBitmap);
COLORREF color = GetPixel(hdcMemory, x, y);
BYTE red = GetRValue(color);
BYTE green = GetGValue(color);
BYTE blue = GetBValue(color);
...
SelectObject(hdcMemory, hBitmapOld);
DeleteObject(hBitmap);
DeleteDC(hdcMemory);
RleaseDC(0, hdcSource);

When you are finished also call DeleteObject(hBitmap)

Using GetDIBits: 32-bit bitmap format is BGRA not RGBA, therefore the first byte is blue, not red. You should specifically request 32-bit bitmap or check the bit-count before treating the bits as 32-bit bitmap.

#include <iostream>
#include <vector>
#include <Windows.h>

int main()
{
    HWND target = FindWindow("Notepad", 0);
    if(!target)
    {
        printf("error, no window\n");
        return 0;
    }

    RECT rc;
    GetWindowRect(target, &rc);
    int x = rc.left;
    int y = rc.top;
    int w = rc.right - rc.left;
    int h = rc.bottom - rc.top;

    int screen_w = GetSystemMetrics(SM_CXFULLSCREEN);
    int screen_h = GetSystemMetrics(SM_CYFULLSCREEN);

    HDC hdc = GetDC(HWND_DESKTOP);
    HBITMAP hbitmap = CreateCompatibleBitmap(hdc, screen_w, screen_h);
    HDC memdc = CreateCompatibleDC(hdc);
    HGDIOBJ oldbmp = SelectObject(memdc, hbitmap);
    BitBlt(memdc, 0, 0, w, h, hdc, x, y, CAPTUREBLT | SRCCOPY);
    SelectObject(memdc, oldbmp);

    BITMAPINFOHEADER infohdr = { sizeof(infohdr), w, h, 1, 32 };
    int size = w * h * 4;
    std::vector<BYTE> bits(size);
    int res = GetDIBits(hdc, hbitmap, 0, h, &bits[0], 
            (BITMAPINFO*)&infohdr, DIB_RGB_COLORS);
    if(res != h)
    {
        std::cout << "error\n";
        return 0;
    }

    BYTE *ptr = bits.data();
    //for(y = 0; y < h; y++)
    for(y = h - 1; y >= 0; y--) //bitmaps bits start from bottom, not top
    {
        for(x = 0; x < w; x++)
        {
            BYTE blu = ptr[0];
            BYTE grn = ptr[1];
            BYTE red = ptr[2];
            ptr += 4;
        }
    }

    SelectObject(memdc, oldbmp);
    DeleteObject(hbitmap);
    ReleaseDC(HWND_DESKTOP, hdc);
    DeleteDC(memdc);

    return 0;
}

Note, the process should be DPI aware in case user is not using default DPI settings.

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • I guess I should add I was trying to avoid getPixel() because I'm scanning a large number of pixels and that would be very slow. I was attempting to extract the RGB data from the bits buffer, compare them and if it's true, return the x y cord of that pixel. – Billy Penley Mar 28 '18 at 20:32
  • GetDIBits will work if you apply the changes I suggested. You have to examine the bitmap's bit-count before proceeding. To compare different bitmaps they must have the same bit-count. – Barmak Shemirani Mar 28 '18 at 22:03
  • I've made the edits to my original post. I'm still getting crazy values. this is very hard to wrap my head around. – Billy Penley Mar 29 '18 at 00:49
  • You are reading the bits the wrong way. Also you have no error check and you are not properly cleaning up the resources properly as I had suggested - see edit. – Barmak Shemirani Mar 29 '18 at 01:58
  • Awesome, this has helped me out so much and the code is working. The last step now is to figure out how to turn the Y Axis upside down considering it seems as though the bitmap is built from the bottom up. – Billy Penley Mar 29 '18 at 21:38
  • And I found another post where you have the inverted function as well. Thank you so much! this has been a headache. – Billy Penley Mar 29 '18 at 21:45
  • I made a mistake in the `for` loop for `y`. Bitmap starts from bottom, not top. See edit. – Barmak Shemirani Mar 31 '18 at 16:38