【问题标题】:GetDIBits and loop through pixels using X, YGetDIBits 并使用 X、Y 遍历像素
【发布时间】:2011-04-10 22:41:34
【问题描述】:

我正在抓取屏幕的一部分并通过像素扫描特定颜色范围。

我看了MSDN's Capturing an Image 的例子,知道如何使用这些函数。

我可以将这些位放入一个数组中,但我不确定如何以一种可以像循环图像一样循环遍历它的方式进行操作。一个伪示例(我敢肯定这很遥远):

for ( x = 1; x <= Image.Width; x += 3 )
{
    for ( y = 1; y <= Image.Height; y += 3 )
    {
        red = lpPixels[x];
        green = lpPixels[x + 1];
        blue = lpPixels[x + 2];
    }
}

这基本上就是我想做的,所以如果红色、蓝色和绿色是某种颜色,我就会知道它在图像中 (x, y) 的坐标。

我只是不知道如何以这种方式使用 GetDIBits,以及如何适当地设置数组以实现此目的。

【问题讨论】:

    标签: c++ loops pixels getdibits


    【解决方案1】:

    除了已经给出的好的答案之外,这里还有一个如何 获得简单数组结构的示例。 (您可以使用例如Goz' code 进行迭代。)

    GetDIBits reference @ MSDN

    您必须选择DIB_RGB_COLORS 作为uUsage 的标志并设置BITMAPINFO structure 和它包含的BITMAPINFOHEADER structure。当您将biClrUsedbiClrImportant 设置为零时,没有“无”颜色表,因此您可以读取从GetDIBits 获得的位图像素作为RGB 值序列。使用32作为位数(biBitCount)根据MSDN设置数据结构:

    位图最多有 2^32 种颜色。如果BITMAPINFOHEADERbiCompression 成员是BI_RGB,则BITMAPINFObmiColors 成员是NULL。位图数组中的每个DWORD 分别代表一个像素的蓝色、绿色和红色的相对强度。每个DWORD中的高字节没有被使用。

    由于 MS LONG 正好是 32 位长(DWORD 的大小),因此您不必注意填充(如 Remarks section 中所述)。

    代码:

    HDC hdcSource = NULL; // the source device context
    HBITMAP hSource = NULL; // the bitmap selected into the device context
    
    BITMAPINFO MyBMInfo = {0};
    MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
    
    // Get the BITMAPINFO structure from the bitmap
    if(0 == GetDIBits(hdcSource, hSource, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS))
    {
        // error handling
    }
    
    // create the pixel buffer
    BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];
    
    // We'll change the received BITMAPINFOHEADER to request the data in a
    // 32 bit RGB format (and not upside-down) so that we can iterate over
    // the pixels easily. 
    
    // requesting a 32 bit image means that no stride/padding will be necessary,
    // although it always contains an (possibly unused) alpha channel
    MyBMInfo.bmiHeader.biBitCount = 32;
    MyBMInfo.bmiHeader.biCompression = BI_RGB;  // no compression -> easier to use
    // correct the bottom-up ordering of lines (abs is in cstdblib and stdlib.h)
    MyBMInfo.bmiHeader.biHeight = abs(MyBMInfo.bmiHeader.biHeight);
    
    // Call GetDIBits a second time, this time to (format and) store the actual
    // bitmap data (the "pixels") in the buffer lpPixels
    if(0 == GetDIBits(hdcSource, hSource, 0, MyBMInfo.bmiHeader.biHeight,
                      lpPixels, &MyBMInfo, DIB_RGB_COLORS))
    {
        // error handling
    }
    // clean up: deselect bitmap from device context, close handles, delete buffer
    

    【讨论】:

    • 在代码中缺少一个初始化为 MyBMInfo .bmiHeader.biSize = sizeof(MyBMInfo ); 的语句
    • @xMRi 你说得对,感谢您发现错误。我认为它现在已修复,我将第一次调用之前的初始化移至 GetDIBits; AFAIK 在第一次调用后没有必要重新初始化它。
    • 嗯。我不确定。我在我的代码中尝试了这个,如果我对 GetDIBits 进行第二次调用,我会收到一个错误,即本地结构在堆栈上获得缓冲区溢出......我终于回到了一个解决方案,即我手动初始化结构。在某些情况下,您不想进行压缩。恢复信息将重用压缩......我不确定这是否会实现。但我没有时间进一步检查。
    • 很好的答案,很有帮助。但我必须删除 32 位的请求,否则第二次 GetDIBits 调用失败。不知道为什么:-)
    • 我遇到了同样的问题。在第一次调用之后 biCompression 设置为 3,因此将其设置为 0 是必不可少的,以避免在结构末尾写入额外的 3 个 DWORD。
    【解决方案2】:

    GetDIBits 返回值的一维数组。对于 M 像素宽、N 像素高并使用 24 位颜色的位图,前 (M*3) 个字节将是第一行像素。后面可以跟一些填充字节。这取决于 BITMAPINFOHEADER。通常有填充以使宽度成为 4 个字节的倍数。因此,如果您的位图是 33 像素宽,那么实际上每行将有 (36*3) 个字节。

    这种“像素加内边距”称为“步幅”。对于 RGB 位图,您可以使用 stride = (biWidth * (biBitCount / 8) + 3) &amp; ~3 计算步幅,其中 biWidthbiBitCount 取自 BITMAPINFOHEADER。

    我不确定你想如何遍历数组。如果你想从左上角到右下角逐个像素(假设这是一个自上而下的位图):

    for (row = 0; row < Image.Height; ++row)
    {
        int rowBase = row*stride;
        for (col = 0; col < Image.Width; ++col)
        {
            red = lpPixels[rowBase + col];
            // etc.
        }
    }
    

    【讨论】:

    • 嗯,这太糟糕了 stride = (biWidth * (biBitCount / 8) + 3) &amp; ~3 - 花了我一段时间才明白(四舍五入到下一个 LONG = 4字节])。但是,这是一种在必要时注意填充的简单方法。
    • 最后是一个考虑到步幅的示例...而不是首先需要转换为 32 位 :)
    • “实际上每行将有 (36*3) 个字节”是不正确的,因为它不会将像素数四舍五入为 4 的倍数,而只是字节数。应用公式(这是正确的)你得到每行 100 个字节而不是 108 个。
    【解决方案3】:

    在您发布的链接中,您创建了一个 32 位位图,因此我假设您正在从 32 位位图读取数据(这个假设可能不正确)。

    因此,将循环更改为以下内容应该可以:

    char* pCurrPixel = (char*)lpPixels;
    for ( y = 0; y < Image.Height; y++ )
    {
        for ( x = 0; x < Image.Width; x++ )
        {
            red = pCurrPixel[0];
            green = pCurrPixel[1];
            blue = pCurrPixel[2];
    
            pCurrPixel += 4;
        }
    }
    

    注意事项:

    1.数组在 C/C++ 中为 0
    2. 每次水平和垂直步进 3 个像素。这意味着您不会访问每个像素。
    3. 位图通常被组织成具有“宽度”像素的“高度”跨度。因此,您应该逐步遍历一个跨度中的每个像素,然后移动到下一个跨度。
    4. 如前所述,确保您正确读取像素。在 16 位模式下它更复杂

    【讨论】:

      【解决方案4】:

      这并不容易。您的算法将取决于图像的颜色深度。如果它是 256 或更少,您将没有像素颜色,但会进入调色板。 16 位像素可以是 RGB555 或 RGB565,24 位图像将是 RGB888,32 位图像将是 RGBA 或 ARGB。您需要 BITMAPINFOHEADER 才能找到答案。

      一旦你发现,像素数据将只是一个大小为宽度 * 高度 * (BitsPerPixel / 8) 的数组

      【讨论】:

      • +1。或者,您可以尝试复制/blit 到具有固定/已知颜色深度的位图。
      • 我认为 OP 将位图选择到 DeviceContext 中,因此可以完全控制位图。使用 24 位且无压缩,可以轻松将其读取为 RGB888。
      【解决方案5】:

      来自 MSDN 的一些惊喜:

      该表由一组 RGBQUAD 数据结构组成。 (桌子 BITMAPCOREINFO 格式是用 RGBTRIPLE 数据构建的 结构。)红色、绿色和蓝色字节顺序相反(红色 根据 Windows 约定用蓝色交换位置。

      所以,在 GetDIBits() 之后,颜色在内存中按 BGR 顺序排列

      【讨论】:

        猜你喜欢
        • 2013-02-20
        • 2018-04-08
        • 2014-12-01
        • 1970-01-01
        • 2013-10-10
        • 2021-08-05
        • 1970-01-01
        • 1970-01-01
        • 2016-07-07
        相关资源
        最近更新 更多