Click to See Complete Forum and Search --> : getting RGB from a 16-bit bitmap


TiMBuS
July 15th, 2005, 08:50 PM
Hey, you guys seem to be the best programmers around, so I've got a question to ask you.

I'm writing a program that detects pixels off the screen, and reacts accordingly. I've designed it to be as fast as possible, so I've done the following:

Made the program wait for printscr to be pressed, allowing windows to copy the data to the clipboard.
Tested, Opened, and Read in the clipboard HANDLE (as a HBITMAP)
called GetDIBits to read in the colour data (the only important part)

Now, I copy the buffer to stdin and fill out a 2d array. its an 800x600 picture at 16-bit colour, so I have a 2d array at [800*2][600] size, 2 bytes (16 bits) per pixel. Ill say this method is VERY fast. it clocks in at about 70ms including my Sleep(50) that I put in to allow time for the screenshot to be copied.

But now I can't read the colour. Thats my problem. With a 24-bit bitmap you can see the R, G, and B quite easily because they have their own bytes for each. All I know is that there is a colour table and this somehow relates to the 16-bits... not helpful eough for me, I'm afraid. How do I turn these 2 bytes into R, G, and B?
Instead should I just 'rebuild' the bitmap into memory, and use GetPixel on it? Got any other ideas for fast pixel scanning?

EDIT - maybe this should be in 'graphics programming' - can a mod move it there?

golanshahar
July 16th, 2005, 02:24 AM
i have a function i once wrote that convert bitmap to 24 bit color, you can take a look and modify it for your purposes. it uses the ::CreateDIBSection(..) api.
all you need to provide to the fuction is the:
const int &nWidth // width of the image

const int &nHeight// height of the image

const HBITMAP // handle to the bitmap of the 16 bit you have ( you can have by ::CreateBitmap(..) api or from ::LoadImage(..) api

BYTE *lpDesBits // out - pointer to 24 bit data bits that you sould alloc before calling the function in a size of witdh*height*3


void Get24BitImage ( const int &nWidth, const int &nHeight,const HBITMAP &hBitmap , BYTE *lpDesBits)
{
HDC hDC = ::GetDC( 0 );

HDC memDC1 = ::CreateCompatibleDC ( hDC );
HDC memDC2 = ::CreateCompatibleDC ( hDC );

BYTE *lpBits = NULL;

BITMAPINFO bmi;
::ZeroMemory( &bmi, sizeof(BITMAPINFO) );
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = nWidth;
bmi.bmiHeader.biHeight = nHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = BI_RGB;

HBITMAP hDIBMemBM = ::CreateDIBSection( 0, &bmi, DIB_RGB_COLORS, (void**)&lpBits, NULL, NULL );
HBITMAP hOldBmp1 = (HBITMAP)::SelectObject(memDC1, hDIBMemBM );

HBITMAP hOldBmp2 = (HBITMAP) ::SelectObject ( memDC2,hBitmap);

::BitBlt( memDC1, 0, 0, nWidth, nHeight, memDC2, 0, 0, SRCCOPY );

for ( int i = 0 ; i < nHeight ; i++)
::CopyMemory(&lpDesBits[i*3*nWidth],&lpBits[nWidth*3*(nHeight-1-i)],nWidth*3);

// clean up
::SelectObject ( memDC1, hOldBmp1 );
::SelectObject ( memDC2,hOldBmp2 );
::ReleaseDC ( 0, hDC );
::DeleteObject ( hDIBMemBM );
::DeleteObject ( hOldBmp1 );
::DeleteObject ( hOldBmp2 );
::DeleteDC ( memDC1 );
::DeleteDC ( memDC2 );
}



Cheers

Marc G
July 16th, 2005, 05:28 AM
Why can't you just use an array with 24 or 32 bit values?

TiMBuS
July 16th, 2005, 05:39 AM
because I'm reading in 16-bit colour from the screen.

golanshahar: thanks a lot man, its helpful, I'll test it to see how fast it is, and if the solution is feasable, then ill rate ;p
PS. Does it output the standard 'upside down' bitmap?

TiMBuS
July 16th, 2005, 06:23 AM
Omg. fast as ever. Props to you man, for making that function which is PERFECT for my needs. Its also **** useful.

Seriously ask microsoft to package that in their GDI, lol.

golanshahar
July 16th, 2005, 07:25 AM
your welcome mate. :)
btw it will convert any image buffer to 24 bit not only 16bit.

Cheers

Marc G
July 16th, 2005, 09:19 AM
because I'm reading in 16-bit colour from the screen.
I see. In that case, there is already a function in the Windows GDI that does exactly that: GetDIBits. One of the parameters specify which color-depth you want. So if you call that function on a 16 bit bitmap and you request 24 bit, the conversion is done for you.

golanshahar
July 16th, 2005, 10:38 AM
I see. In that case, there is already a function in the Windows GDI that does exactly that: GetDIBits. One of the parameters specify which color-depth you want. So if you call that function on a 16 bit bitmap and you request 24 bit, the conversion is done for you.


not quite true, AFAIK GetDIBits alone wont do the work ;) you can use it in the end only after you call the CreateDIBSection and specifed the format in there, then the BitBlt does the convertion.
i know that instead of BitBlt we one could use the SetDIBitsToDevice ( which can also convert )

Cheers

philkr
July 17th, 2005, 09:05 AM
Although your problem is already solved, I wanted to add something, because you were talking about performance and the above solution is a bit like shooting birds with cannons, if you really only want to get the RGB components of a 16-bit bitmap.
A 16-bit bitmap does not use a colortable (that is the case for 8-bit bitmaps). It also uses the bits to describe the red, green and blue value of the pixel directly. For 32-bit you had 8 bit for red, 8 bit for green, 8 bit for blue and 8 bit for alpha (transparency). For 24-bit you had the same without alpha. Now for 16-bit you have two possible formats, because you can't divide the 16 bits by 3.
1) Format 555: 5 bits for each color, 1 bit is unused.
2) Format 565: 5 bits red, 6 green, 5 blue
I think windows uses the second format 565, because it provides a more exact color information for the green portion. But let's go practic:

Given the information above you can easiliy calculate the color components of a 16 bit color value like this:

nRed = (nColor & 0xF800) >> 11;
nGreen = (nColor & 0x07E0) >> 5;
nBlue = nColor & 0x1F;

TiMBuS
July 17th, 2005, 09:20 AM
thanks philkr, thats a very handy alternative method, and its great to finally crack the 16-bit code =)

ty.

golanshahar
July 17th, 2005, 09:43 AM
Although your problem is already solved, I wanted to add something, because you were talking about performance and the above solution is a bit like shooting birds with cannons, if you really only want to get the RGB components of a 16-bit bitmap.
A 16-bit bitmap does not use a colortable (that is the case for 8-bit bitmaps). It also uses the bits to describe the red, green and blue value of the pixel directly. For 32-bit you had 8 bit for red, 8 bit for green, 8 bit for blue and 8 bit for alpha (transparency). For 24-bit you had the same without alpha. Now for 16-bit you have two possible formats, because you can't divide the 16 bits by 3.
1) Format 555: 5 bits for each color, 1 bit is unused.
2) Format 565: 5 bits red, 6 green, 5 blue
I think windows uses the second format 565, because it provides a more exact color information for the green portion. But let's go practic:

Given the information above you can easiliy calculate the color components of a 16 bit color value like this:


nRed = (nColor & 0xF800) >> 11;
nGreen = (nColor & 0x07E0) >> 5;
nBlue = nColor & 0x1F;



its pretty obvious that working on raw bits is faster than bliting however:
- as i mentioned above you can use ::SetDibBitsToDevice() which is faster then BitBlt.
- although your idea is faster the probelm is that you have to use cases (for 8,16 bits etc) since you de-composing the bits by yourself and not letting DIB to do the convertion. while in my function it will work on every input format.

btw yes windows is working on 565 bit format ;)

Cheers

philkr
July 17th, 2005, 01:22 PM
You are perfectly right, although using cases would not be a problem for most configurations. You would only have to call GetDeviceCaps(hdc, BITSPIXEL). If you care about the small number of users with 256 colors, your method would be more comfortable, I think.

TiMBuS
July 17th, 2005, 11:44 PM
hey, philkr, you seem to forget that i'm pulling the bitmap off of the clipboard, so I need to copy the bitmap somehow anyway (because I don't think leaving the clipboard open is in any way a good idea).
Also, golanshahar, why is my output... odd.
I run your function, fill in a unsigned int struct called DIBits, then do the following:
for (int y = 0; y < 600; ++y)
{
for (int x = 0; x < 800; ++x)
{
if (RGB(DIBits[x*3+(y*800*3)+2], DIBits[x*3+(y*800*3)+1], DIBits[x*3+(y*800*3)]) == RGB(255, 255, 255))
{
hdc = GetDC(NULL);
SetPixel(hdc, x, y, RGB(255, 0, 0));
ReleaseDC(NULL, hdc);
}
}
}
It should 'paint' every white pixel on screen red, yes? I'm sure that I'm pointing it to the correct position on the array, too.. But it's not working right. Is my test function wrong, or is the Get24BitImage function wrong?
The general error is that the painted pixels are too high up. the image seems vertically compressed.

also, the result seems to 'tile' so I get repeating patterns painted.

TiMBuS
July 18th, 2005, 03:41 AM
Well after a bit of testing with above code, I replaced the 'if' paramaters so it simply showed if (DIBits)
And I noticed that the screen only colours 450 pixels down, not 600. Quick calculation: 450/600 = 3/4, hence meaning that the output is missing one byte per pixel. Your function may return the three RGB colours but its missing the AA placeholders, and I'm not yet sure if your result skews the table by returning, say BBGGRR for pixel 1, then AABBGG for pixel 2, RRAABB for pixel 3, etc.

golanshahar
July 18th, 2005, 04:01 AM
hi mate, the function should work i took out of project of mine i did couple of years ago and it work. i dont know how you use it exactly and if you passing corret params.

send your project and i will look into it.

Cheers

TiMBuS
July 18th, 2005, 06:14 AM
dont worry about it now man, I got it all going and I'm more than happy about that :)
thanks again for the function, it's just fine, I had a few x, y values swapped, is all, thats why the screen only had 3/4 of the pixels.

golanshahar
July 18th, 2005, 10:10 AM
ok good to hear it worked out for you. :)
anytime mate.

Cheers

mauigb
October 23rd, 2005, 10:52 PM
I know you solved your problem TiMBuS, but I thought I would post a solution/improvement for all the novices like me who need to see it so we can learn it!

This is the entire OnClick() event for a button on my main form:



void __fastcall TFrmMain::Button3Click(TObject *Sender)
{
TPoint ptOrigin = FrmMain->GetClientOrigin(); //This ensures all drawing begins in the
ptOrigin.x+=PaintBox->Left; //client area of MY form. my 'lpCapturedBits'
ptOrigin.y+=PaintBox->Top; //was captured using user32.dll PrintWindow()
//and represents ONLY an alien window, NOT the
//whole screen! You can omit this, and set the
//rcDrawArea TRect to the entire screen to
//accomplish what TiMBuS was trying. I created
//ptOrigin, rcDrawArea, BitsPerPixel, and
//BitPosition for 3 reasons: To eliminate
//redundancy, to make modifications easier, and
//to make this code easier to read! NOTE: PaintBox
//is a TPaintbox, and I am using C++ Builder 6.0
//on Windows XP--->Thus the call to PrintWindow()...
TRect rcDrawArea;
rcDrawArea.left=ptOrigin.x;
rcDrawArea.top=ptOrigin.y;
rcDrawArea.right=ptOrigin.x+PaintBox->Width;
rcDrawArea.bottom=ptOrigin.y+PaintBox->Height;

int BitsPerPixel=4; //Add function here to determine screen color depth...Mine is 32 Bit
//32 Bit Color Depth: BitsPerPixel=4
//24 Bit Color Depth: BitsPerPixel=3
//16 Bit Color Depth: BitsPerPixel=2
//THIS IS WHERE TiMBuS had his problem! In fact, merely replacing 3
//as a multiplier in his code might have been enough for him. I
//think this code is more flexible, and should be faster, though!
//PS: I tried it and it worked GREAT for me!

int BitPosition=0;

for (int y = 0; y < rcDrawArea.Height(); ++y)
{
for (int x = 0; x < rcDrawArea.Width(); ++x)
{ //BitPosition avoids the same math being done 3x in a row, with the same values!
BitPosition=x*BitsPerPixel+(y*rcDrawArea.Width()*BitsPerPixel);
if (RGB(lpCapturedBits[BitPosition+2], lpCapturedBits[BitPosition+1], lpCapturedBits[BitPosition]) == RGB(255, 255, 255))
{
hdc = GetDC(NULL);
SetPixel(hdc, ptOrigin.x+x, ptOrigin.y+y, RGB(255, 0, 0));//You can Omit ptOrigin if using the WHOLE SCREEN
ReleaseDC(NULL, hdc);
}
}
}
}




My notes and explanations are in the code... My code, in particular, uses a capture of an alien window via User32.dll PrintWindow() to set the value of lpCapturedBits[]. I THINK you need to be running Windows XP to call that function... here is the breakdown on how to access lpCapturedBits, As I have seen it referenced MANY times in these posts, and never explained for those of use who still need the Dummy Books...



int BitsPerPixel=4;
int captureSize = BitsPerPixel * captureWidth * captureHeight;
lpCapturedBits = new char[captureSize];

//'lpCapturedBits' is is my image data array, it is globally declared

GetBitmapBits(memBM, captureSize, lpCapturedBits);



'lpCapturedBits' is basically raw bitmap pixel bits stacked inside... For example, with bitPosition determined by desired pixel coords, and bits per pixel:



int bitPosition = ( ( y*captureWidth )+x )*BitsPerPixel;
//You may have to fudge your x,y values a bit... I had y=y+3 in this equation
//to make my image line up, due to OTHER FACTORS

lpCapturedBits[BitPosition]=blueBit;
lpCapturedBits[BitPosition+1]=greenBit;
lpCapturedBits[BitPosition+2]=redBit;
lpCapturedBits[BitPosition+3]=alphaBit; //I don't use this, it is the transparency channel,
//and the elusive "4th bit" that causes so many
//beginners like me headaches!

int PixelColor = RGB( redBit, greenBit, blueBit );



I am just learning this stuff, so I figure as a novice, I can explain it to other novices!

~Maui

Xatrix
January 3rd, 2006, 08:20 AM
Hi,

sorry to bump the old thread, but just thought better to post here than start a new thread where I'll be told to do a forum search ... I've just did, plus google search ;)

Anyway, I just don't get it, it seems that all articles are missing some vital info which holds me off from continuing my work. What I'm trying to do is a simple thing (which became complicated) - get RGB values from a 16-bit 565 WORD value. Here's the problem. I have 3 variables (red, green, blue) of WORD type, which contain R, G and B values respectively. I pack these three values based on the algorythm found in MSDN (see source code). I get a 16-bit value containing all 3 RGB components. Then I'm trying to decode it back to separate R, G and B values, and I'm not getting those, even remotely.

Initial RGB values are - 100 110 120 (dec)
After decomposing I get 5 47 24 (dec)

Below is the source code:


#include <iostream.h>
#include <windows.h>

int main ()
{

WORD red = 100;
WORD green = 110;
WORD blue = 120;

WORD pixel565 = (red << 11) | (green << 5) | blue;

WORD red_mask = 0xF800;
WORD green_mask = 0x7E0;
WORD blue_mask = 0x1F;

BYTE red_value = (pixel565 & red_mask) >> 11;
BYTE green_value = (pixel565 & green_mask) >> 5;
BYTE blue_value = pixel565 & blue_mask;

cout << "R = " << (int)red_value << "\n";
cout << "G = " << (int)green_value << "\n";
cout << "B = " << (int)blue_value << "\n";

return 0;
}


Was is wrong with it? Something is obviously missing. But all the articles explain only that, so from their words this should be enough...well, maybe but it just doesn't work :(

Any help is much appreciated,
Oleg

Marc G
January 3rd, 2006, 08:46 AM
Before "WORD pixel565 = (red << 11) | (green << 5) | blue;" you need to make sure the red, green and blue values are truncated to the appropriate size: red and blue are 5 bits, green is 6 bits.

Xatrix
January 3rd, 2006, 08:57 AM
ohh, that's it! Thank you, Marc! :wave:
(wish it would have been mentioned in other sources cause that was the vital info which was omitted)

Partizan
January 4th, 2006, 03:26 PM
Sorry, for resurrecting this old topic, but it showed up as "HOT THREADS..." in my CodeGuru Newsletter which I was reading because I was bored. :)

I have a question about golanshahar's method though. I always thought that if you create a compatible DC context it will be in the same color format as the source (i.e. Display). In your code you create a compatible device context which could be of any color depth (8, 16, 24, 32 bpp) and selecting a 24 bpp bitmap into it.

What happens if you set your display to 256 colors and run this sample? Since you are delaing with device dependent bitmaps, wouldn't the colors be really distorted? Or am I missing something?

i have a function i once wrote that convert bitmap to 24 bit color, you can take a look and modify it for your purposes. it uses the ::CreateDIBSection(..) api.
all you need to provide to the fuction is the:
const int &nWidth // width of the image

const int &nHeight// height of the image

const HBITMAP // handle to the bitmap of the 16 bit you have ( you can have by ::CreateBitmap(..) api or from ::LoadImage(..) api

BYTE *lpDesBits // out - pointer to 24 bit data bits that you sould alloc before calling the function in a size of witdh*height*3


void Get24BitImage ( const int &nWidth, const int &nHeight,const HBITMAP &hBitmap , BYTE *lpDesBits)
{
HDC hDC = ::GetDC( 0 );

HDC memDC1 = ::CreateCompatibleDC ( hDC );
HDC memDC2 = ::CreateCompatibleDC ( hDC );

BYTE *lpBits = NULL;

BITMAPINFO bmi;
::ZeroMemory( &bmi, sizeof(BITMAPINFO) );
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = nWidth;
bmi.bmiHeader.biHeight = nHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = BI_RGB;

HBITMAP hDIBMemBM = ::CreateDIBSection( 0, &bmi, DIB_RGB_COLORS, (void**)&lpBits, NULL, NULL );
HBITMAP hOldBmp1 = (HBITMAP)::SelectObject(memDC1, hDIBMemBM );

HBITMAP hOldBmp2 = (HBITMAP) ::SelectObject ( memDC2,hBitmap);

::BitBlt( memDC1, 0, 0, nWidth, nHeight, memDC2, 0, 0, SRCCOPY );

for ( int i = 0 ; i < nHeight ; i++)
::CopyMemory(&lpDesBits[i*3*nWidth],&lpBits[nWidth*3*(nHeight-1-i)],nWidth*3);

// clean up
::SelectObject ( memDC1, hOldBmp1 );
::SelectObject ( memDC2,hOldBmp2 );
::ReleaseDC ( 0, hDC );
::DeleteObject ( hDIBMemBM );
::DeleteObject ( hOldBmp1 );
::DeleteObject ( hOldBmp2 );
::DeleteObject ( memDC1 );
::DeleteObject ( memDC2 );
}



Cheers

golanshahar
January 4th, 2006, 04:07 PM
Sorry, for resurrecting this old topic, but it showed up as "HOT THREADS..." in my CodeGuru Newsletter which I was reading because I was bored. :)


dont you have something better to do when you are bored :D ?
kidding.


I have a question about golanshahar's method though. I always thought that if you create a compatible DC context it will be in the same color format as the source (i.e. Display). In your code you create a compatible device context which could be of any color depth (8, 16, 24, 32 bpp) and selecting a 24 bpp bitmap into it.

What happens if you set your display to 256 colors and run this sample? Since you are delaing with device dependent bitmaps, wouldn't the colors be really distorted? Or am I missing something?

well no you are not missing something. in fact you are right. however dont forget that this answer was given to a specific question :)

if you seek for better solution that is not device depended the better/fast way is to work with the raw bits and simply turn each RGB24 to RGB16 , here look at this sample that i worte:


void ColorConversion24To16 ( BYTE *lpBits24, int Width, int Height, BYTE *lpBits16 )
{
int nPos16 = 0;
int Size = Width*Height*3;
for ( int nPos24 = 0 ; nPos24 < Size ; nPos24+=3 ,nPos16+=2)
{
BYTE Red24 = lpBits24[nPos24+2]; // 8-bit red
BYTE Green24 = lpBits24[nPos24+1]; // 8-bit green
BYTE Blue24 = lpBits24[nPos24+0]; // 8-bit blue

BYTE Red16 = Red24 >> 3; // 5-bit red
BYTE Green16 = Green24 >> 2; // 6-bit green
BYTE Blue16 = Blue24 >> 3; // 5-bit blue

unsigned short RGB2Bytes = Blue16 + (Green16<<5) + (Red16<<(5+6));

lpBits16[nPos16] = LOBYTE(RGB2Bytes);
lpBits16[nPos16+1]= HIBYTE(RGB2Bytes);
}
}

// usage
int Width = 100;
int Height = 100;

BYTE *lpBits24 = new BYTE[Width*Height*3];
BYTE *lpBits16 = new BYTE[Width*Height*2];


ColorConversion24To16( lpBits24,Width,Height,lpBits16);


delete [] lpBits24;
delete [] lpBits16;


it getting two raw bits (input as 24 and output as 16) and simply convert each 24 bit pixel to 16 bit.

NOTE: i didnt test it much but it should work.

/EDIT Opps i just realized that i coverted the other way around ( from 24 to 16 instead 16 to 24 ) i need to get some sleep. :D but anyway you got the idea.

Cheers

Partizan
January 4th, 2006, 04:48 PM
well no you are not missing something. in fact you are right. however dont forget that this answer was given to a specific question :)
In that case you threw me off with one of your replies to philkr, saying that your method will work with ANY bitmap. You should note that it will work with "any" bitmap only if you are upconverting that "any" bitmap. :)

if you seek for better solution that is not device depended the better/fast way is to work with the raw bits and simply turn each RGB24 to RGB16 , here look at this sample that i worte:
Thanks. I'm using similar in-memory manipulations on DIBs to change color depth. I find this method fast and accurate.

golanshahar
January 4th, 2006, 04:57 PM
In that case you threw me off with one of your replies to philkr, saying that your method will work with ANY bitmap. You should note that it will work with "any" bitmap only if you are upconverting that "any" bitmap. :)


i meant to every input format!

...while in my function it will work on every input format...

which is true. (while your dc is compatible with 24 )

Cheers