10

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

我查看了MSDN 的 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,以及如何适当地设置数组以实现这一点。

4

5 回答 5

15

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

GetDIBits 参考@MSDN

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

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

由于 MSLONG的长度正好为 32 位(大小为 a DWORD),因此您不必注意填充(如备注部分所述)。

代码:

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
于 2010-09-10T22:02:36.910 回答
10

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

这种“像素加填充”称为“步幅”。对于 RGB 位图,您可以计算步幅:stride = (biWidth * (biBitCount / 8) + 3) & ~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.
    }
}
于 2010-09-10T21:41:15.063 回答
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.Arrays 是 0 基于 C/C++
2. 你每次水平和垂直步进 3 个像素。这意味着您不会访问每个像素。
3. 位图通常被组织成具有“宽度”像素的“高度”跨度。因此,您应该逐步遍历一个跨度中的每个像素,然后移动到下一个跨度。
4. 如前所述,确保您正确读取像素。在 16 位模式下它更复杂

于 2010-09-10T21:41:56.677 回答
2

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

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

于 2010-09-10T21:25:42.347 回答
2

来自 MSDN 的一些惊喜:

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

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

于 2014-11-02T16:21:00.757 回答