C++ Getting RGB from hBitmap
Asked Answered
L

2

10

Working with bitmaps is very new to me so I've been really struggling with the online tutorials and strategies that I've read through. Basically my goal is to scan the screen for a particular RGB value. I believe the steps to do this is to capture the screen in a hBitmap and then produce an array of RGB values from it that I can scan through.

I originally started with GetPixel but that is very slow. The solution was to use GetDIBits which produces the array of RGB values. The problem is that it returns weird and possibly random RGB values instead.

I'm using the following code which I found from another tutorial:

/* Globals */
int ScreenX = GetDeviceCaps(GetDC(0), HORZRES);
int ScreenY = GetDeviceCaps(GetDC(0), VERTRES);
BYTE* ScreenData = new BYTE[3*ScreenX*ScreenY];

void ScreenCap() {
    HDC hdc = GetDC(GetDesktopWindow());
    HDC hdcMem = CreateCompatibleDC (hdc);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdc, ScreenX, ScreenY);
    BITMAPINFOHEADER bmi = {0};
    bmi.biSize = sizeof(BITMAPINFOHEADER);
    bmi.biPlanes = 1;
    bmi.biBitCount = 24;
    bmi.biWidth = ScreenX;
    bmi.biHeight = -ScreenY;
    bmi.biCompression = BI_RGB;
    bmi.biSizeImage = ScreenX * ScreenY;
    SelectObject(hdcMem, hBitmap);
    BitBlt(hdcMem, 0, 0, ScreenX, ScreenY, hdc, 0, 0, SRCCOPY);
    GetDIBits(hdc, hBitmap, 0, ScreenY, ScreenData, (BITMAPINFO*)&bmi, DIB_RGB_COLORS);

    DeleteDC(hdcMem);
    ReleaseDC(NULL, hdc);
}

inline int PosR(int x, int y) {
    return ScreenData[3*((y*ScreenX)+x)+2];
}

inline int PosG(int x, int y) {
    return ScreenData[3*((y*ScreenX)+x)+1];
}

inline int PosB(int x, int y) {
    return ScreenData[3*((y*ScreenX)+x)];
}

I test this with the following code. I hit Shift to call ScreenCap and then I move my cursor to the desired location and hit Space to see what the RGB value is at that location. Am I completely nuts?

int main() {

while ( true ) {

   if (GetAsyncKeyState(VK_SPACE)){  

      // Print out current cursor position
      GetCursorPos(&p);
      printf("X:%d Y:%d \n",p.x,p.y);
      // Print out RGB value at that position
      int r = PosR(p.x, p.y);
      int g = PosG(p.x, p.y);
      int b = PosB(p.x, p.y);
      printf("r:%d g:%d b:%d \n",r,g,b);

   } else if (GetAsyncKeyState(VK_ESCAPE)){
      printf("Quit\n");
      break;
   } else if (GetAsyncKeyState(VK_SHIFT)){
      ScreenCap();
      printf("Captured\n");
   }
}

system("PAUSE");
return 0;
}
Lucent answered 19/4, 2013 at 19:49 Comment(3)
You are requesting RGB, but your code seems to be treating the data as BGR.Travancore
From what I've read, I believe the nature of GetDIBits returns them in that order. But I guess I should point out that even if I tested on a completely black/white screen where all r=g=b, the rgb values still seem to be random. So it would report black when it's actually white, and white when it's actually black sometimes.Lucent
possible duplicate of GetDIBits and loop through pixels using X, YKukri
B
11

The issue is that your screen is actually 32bits deep not 24. The code below will give you the result you need:

/* Globals */
int ScreenX = 0;
int ScreenY = 0;
BYTE* ScreenData = 0;

void ScreenCap() 
{
    HDC hScreen = GetDC(NULL);
    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);
    DeleteObject(hBitmap);
}

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) 
    {
       if (ButtonPress(VK_SPACE))
       {  

          // Print out current cursor position
          POINT p;
          GetCursorPos(&p);
          printf("X:%d Y:%d \n",p.x,p.y);
          // Print out RGB value at that position
          std::cout << "Bitmap: r: " << PosR(p.x, p.y) << " g: " << PosG(p.x, p.y) << " b: " << PosB(p.x, p.y) << "\n";

       } else if (ButtonPress(VK_ESCAPE))
       {
          printf("Quit\n");
          break;
       } else if (ButtonPress(VK_SHIFT))
       {
          ScreenCap();
          printf("Captured\n");
       }
    }

    system("PAUSE");
    return 0;
}
Barnaul answered 20/4, 2013 at 1:56 Comment(1)
C
1

Your image size is specified in pixels, it should be specified in bytes

**bmi.biSizeImage = ScreenX * ScreenY;**
**bmi.biBitCount = 24;**
bmi.biWidth = ScreenX;
bmi.biHeight = -ScreenY;
**bmi.biCompression = BI_RGB;**

biSizeImage its defined units are bytes and you are specifying RGB 3 bytes per pixel.

http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx

biSizeImage The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps.

Commissary answered 19/4, 2013 at 21:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.