从hBitmap获取RGB的C++方法

10
处理位图对我来说非常新颖,所以我一直在努力学习在线教程和阅读的策略。我的目标基本上是扫描屏幕以查找特定的RGB值。我相信要做到这一点的步骤是捕获屏幕中的hBitmap,然后从中产生一个RGB值的数组,我可以通过它进行扫描。
我最初使用的是GetPixel,但那非常慢。解决方案是使用GetDIBits来生成RGB值的数组。问题在于它返回了奇怪且可能是随机的RGB值。
我正在使用另一个教程中找到的以下代码:
/* 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)];
}

我用以下代码进行测试。 我按Shift键调用ScreenCap,然后将光标移动到所需位置,并按Space键以查看该位置的RGB值。 我疯了吗?

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

您正在请求RGB,但您的代码似乎将数据视为BGR。 - Drew Dormann
从我阅读的内容来看,我相信GetDIBits的本质是按照那个顺序返回它们的。但我想我应该指出,即使我在一个完全黑白的屏幕上进行测试,其中所有r=g=b,rgb值似乎仍然是随机的。因此有时它会报告黑色,而实际上是白色,有时会报告白色,而实际上是黑色。 - Mike
可能是GetDIBits和使用X,Y循环遍历像素的重复问题。 - sashoalm
2个回答

11
问题在于您的屏幕实际上是32位深度而不是24位。下面的代码将给出您所需的结果:
/* 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;
}

1
显然这个解决方案中存在内存泄漏的 bug,但现在已经修复了。 - user456814

1

您的图片尺寸是以像素为单位指定的,应该使用字节来指定。

**bmi.biSizeImage = ScreenX * ScreenY;**
**bmi.biBitCount = 24;**
bmi.biWidth = ScreenX;
bmi.biHeight = -ScreenY;
**bmi.biCompression = BI_RGB;**
biSizeImage 定义的单位是字节,并且您正在指定每个像素的 RGB 3 字节。

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

biSizeImage图像的大小,以字节为单位。这可以设置为零,用于BI_RGB位图。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接