GetDIBits and loop through pixels using X, Y
Asked Answered
M

5

11

I'm grabbing a portion of the screen and scanning through the pixels for a certain color range.

I looked at MSDN's Capturing an Image example and know how to use the functions.

I can get the bits into an array, but I'm not sure how to do it in such a way that I can loop through it as I would an image. A pseudo-example (which I'm sure is way off):

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

That's basically what I want to do, so if red, blue, and green is a certain color, I'll know what coordinate it's at (x, y) in the image.

I just don't know how to use GetDIBits in such a way, and how to setup the array appropriately to be able to accomplish this.

Muskogee answered 10/9, 2010 at 21:17 Comment(0)
A
17

Apart from the good answers already given, here's an example of how to get a simple array structure to walk on. (You can use e.g. Goz' code for the iteration.)

GetDIBits reference @ MSDN

You have to select DIB_RGB_COLORS as flag for uUsage and set up the BITMAPINFO structure and the BITMAPINFOHEADER structure it contains. When you set biClrUsed and biClrImportant to zero, there is "no" color table, so you can read the pixels of the bitmap you get from GetDIBits as a sequence of RGB values. Using 32 as bit count (biBitCount) sets up the data structure according to MSDN:

The bitmap has a maximum of 2^32 colors. If the biCompression member of the BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is NULL. Each DWORD in the bitmap array represents the relative intensities of blue, green, and red, respectively, for a pixel. The high byte in each DWORD is not used.

Since a MS LONG is exactly 32 bit long (the size of a DWORD), you do not have to pay attention to padding (as described in the Remarks section).

Code:

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
Adallard answered 10/9, 2010 at 21:17 Comment(6)
In the code there is a missing statement that initializes to MyBMInfo .bmiHeader.biSize = sizeof(MyBMInfo );Brina
@Brina You're right, thanks for spotting the error. I think it's fixed now, I moved the initialization before the first call to GetDIBits; AFAIK it's not necessary to re-initialize it after the first call.Adallard
Hmmm. I am not sure about that. I tried this in my code and if I make the second call to GetDIBits I get an error that the local structure gets a buffer Overflow on the stack... I finally returned to a solution were I initialize the structure manualy. In some cases you don't want to have an compression. Resuing the informatiuon will reuse the compression... I am not sure if this Comes true. But I didn't had time to check this further.Brina
Great answer, very helpful. But I had to remove the request for 32bit, otherwise the 2nd GetDIBits call failed. No clue why :-)Shoebill
I had the same problem. After the 1st call biCompression is set to 3, so setting it to 0 is essential in order to avoid extra 3 DWORDs to be written at the end of structure.Bobstay
@user64985 Unfortunately, I don't have a Windows development environment any more to maintain this answer. IIRC, and looking at Jim Mischel's answer, 24 bit can (or even does) imply padding. According to MSDN, the biHeight can be negative, depending on the memory layout of the original DIB.Adallard
L
10

GetDIBits returns a one-dimensional array of values. For a bitmap that's M pixels wide by N pixels tall and uses 24-bit color, the first (M*3) bytes will be the first row of pixels. That can be followed by some padding bytes. It depends on the BITMAPINFOHEADER. There is usually padding to make the width a multiple of 4 bytes. So if your bitmap is 33 pixels wide, there will actually be (36*3) bytes per row.

This "pixels plus padding" is called the "stride". For RGB bitmaps, you can calculate stride with: stride = (biWidth * (biBitCount / 8) + 3) & ~3, where biWidth and biBitCount are taken from the BITMAPINFOHEADER.

I'm not sure how you want to traverse the array. If you want to go pixel-by-pixel from top left to lower right (assuming this is a top-down bitmap):

for (row = 0; row < Image.Height; ++row)
{
    int rowBase = row*stride;
    for (col = 0; col < Image.Width; ++col)
    {
        red = lpPixels[rowBase + col];
        // etc.
    }
}
Lanugo answered 10/9, 2010 at 21:41 Comment(3)
huh this is awful <code>stride = (biWidth * (biBitCount / 8) + 3) & ~3</code> - took me a while to understand (rounds up to the next LONG = 4 [bytes]). But an easy way to pay attention to padding when you have to.Adallard
finally an example that takes stride into consideration...instead of requiring a conversion to 32 bit first :)Dissidence
"there will actually be (36*3) bytes per row" is incorrect, as it doesn't round the number of pixels to a multiple of 4, just the number of bytes. Applying the formula (which IS correct) you get 100 bytes per row not 108.Hammerlock
H
3

In the link you post you create a 32-bit bitmap so I'll assume you are reading from a 32-bit bitmap (This assumption may be incorrect).

Therefore changing your loop to the following should work:

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

Things to bear in mind:

1.Arrays are 0 based in C/C++
2. You were stepping 3 pixels horizontally and vertically each time. Which meant you aren't visiting every pixel.
3. A bitmap is usually organised such that there are "height" spans of "width" pixels. Therefore you should step through each pixel in a span and then move to the next span.
4. As already pointed out make sure you aare reading pixels correctly. in 16-bit mode its more complex

Hod answered 10/9, 2010 at 21:41 Comment(0)
A
2

It's not so easy. Your algorithm will depend on the colour depth of the image. If it's 256 or less you won't have pixel colours, but indeces into a palette of colours. 16-bit pixels could be RGB555 or RGB565, 24-bit images will be RGB888, and 32-bit images will be RGBA or ARGB. You'll need the BITMAPINFOHEADER to find out.

Once you find out, the pixel data will just be an array of size width * height * (BitsPerPixel / 8)

Antoniaantonie answered 10/9, 2010 at 21:25 Comment(2)
+1. Alternatively you can try copy/blit to a bitmap with fixed/known color depth.Crawly
I think the OP selects the bitmap into the DeviceContext, so has full control on the bitmap. Using 24 bit and no compression allow to read it as RGB888 easily.Adallard
S
2

Some surprise from MSDN:

The table consists of an array of RGBQUAD data structures. (The table for the BITMAPCOREINFO format is built with the RGBTRIPLE data structure.) Red, green, and blue bytes are in reverse order (red swaps position with blue) from the Windows convention.

so, colors are in BGR order in memory after GetDIBits()

Sesquioxide answered 2/11, 2014 at 16:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.