Search code examples
c++colorsbitmappixel

Scan BitMap for Specific pixel color, and give x, y location in c++


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

Solution

  • 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.