32 and 16-bit Bitmaps

Hi, i’m making my own function to read a Bitmap, and i face to a problem.

I already did implementation to read 8-Bit and 24-Bit bitmaps, and now, I’m going to do for 16-bit and 32-bit.

As reference, I’m using a MS WORD document I got at Wotsit’s Format (http://www.wotsit.org/search.asp?s=graphics).

OK, everything is OK, but I didn’t understand what the BI_BITFIELDS (of biCompression - value 0x03) do.

Look at what they say:

“If the Compression field of the bitmap is set to BI_BITFIELDS, the Palette field contains three dword color masks that specify the red, green, and blue components, respectively, of each pixel. Each dword in the bitmap array represents a single pixel.”

What Do I have to do with these 3 values? Do I have to apply a MASK with this in each pixel of the bitmap? If so, How Do I do this?

Thank you a lot gusy, and sorry for the long text.

Fernando

A 16 bit BMP stores a single pixel in one 32 bit DWORD. Those three values specify how many bits of the pixel DWORD are for the red, how many for blue, and how many for green. They also specify where in the DWORD the channels are located.

For example say you have these color masks:

red: 0111110000000000
green: 0000001111100000
blue: 0000000000011111

This specifies that 5 bits of the DWORD are for the red channel, 5 for green, and 5 for blue.

[This message has been edited by jra101 (edited 06-27-2002).]

A 16 bit BMP stores a single pixel in one 32 bit DWORD?
A 16 bit BMP has 16 bits of color information per pixel. They are encode in 5,5,5 scheme, with 5 bits per color component.

unsigned char *CImage::LoadBMP16bpp( FILE *sFileHandle, BITMAPINFOHEADER *spInfoHeader )
{
int i;

//
// Skip Palette if necessary
//

int iNumPaletteEntries = spInfoHeader->biClrUsed;
fseek( sFileHandle, iNumPaletteEntries * sizeof( RGBQUAD ), SEEK_CUR );

//
// read index array from bitmap file
//

int iNumPixels = spInfoHeader->biWidth * spInfoHeader->biHeight;
int iImageBytes = iNumPixels*2;
int iReadRowBytes = spInfoHeader->biWidth * 2;

unsigned short ushortpColorArray = (unsigned short ) malloc( iImageBytes );

if( iReadRowBytes%4 ) {
// padded lines
for( i=0; ibiHeight; i++ ) {
fread( ushortpColorArray + i*iReadRowBytes, 1, iReadRowBytes, sFileHandle );

  	fseek( sFileHandle, 4-(iReadRowBytes%4), SEEK_CUR );
  }

} else {
// unpadded lines
fread( ushortpColorArray, 1, iImageBytes, sFileHandle );
}

//
// build BGR array
//
unsigned short ushortFiveBitMask = 0x1F;
unsigned char *ucpImageArray = (unsigned char *) malloc( iNumPixels * 3 );

for( i=0; i<iNumPixels; i++ ) {
ucpImageArray[ i3 + 0 ] = (ushortpColorArray[i]>>10) & ushortFiveBitMask;
ucpImageArray[ i
3 + 1 ] = (ushortpColorArray[i]>> 5) & ushortFiveBitMask;
ucpImageArray[ i*3 + 2 ] = (ushortpColorArray[i]>> 0) & ushortFiveBitMask;
}

//
// free up and return
//

free( ushortpColorArray );

return ucpImageArray;
}

Ahhh, now i got the inital question.
These bitfields are effectively masks. You can make it this way:

(pixel & mask) >> (mask_zero_bits);

However, this format is (very very) rarely used, and it’s not compatible to windows 9x.
Here’s some code for that:

unsigned char *CImage::LoadBMP16bppBIT( FILE *sFileHandle, BITMAPINFOHEADER *spInfoHeader )
{
int i,j;

//
// read and process the three color masks
//
unsigned short uintColorMasks[3];
int iColorShifts[3];

uintColorMasks[0] = file_read_uint( sFileHandle );
uintColorMasks[1] = file_read_uint( sFileHandle );
uintColorMasks[2] = file_read_uint( sFileHandle );

for( i=0; i<3; i++ ) {
for( j=0; !((uintColorMasks[i]>>j) & 1) && (j < 16); j++ );
iColorShifts[i]=j;
}

//
// Skip Palette following color masks, if any
//

int iNumPaletteEntries = spInfoHeader->biClrUsed;
fseek( sFileHandle, iNumPaletteEntries * sizeof( RGBQUAD ), SEEK_CUR );

//
// read index array from bitmap file
//

int iNumPixels = spInfoHeader->biWidth * spInfoHeader->biHeight;
int iImageBytes = iNumPixels*2;
int iReadRowBytes = spInfoHeader->biWidth * 2;

unsigned short ushortpColorArray = (unsigned short ) malloc( iImageBytes );

if( iReadRowBytes%4 ) {
// padded lines
for( i=0; ibiHeight; i++ ) {
fread( ushortpColorArray + i*iReadRowBytes, 1, iReadRowBytes, sFileHandle );

  	fseek( sFileHandle, 4-(iReadRowBytes%4), SEEK_CUR );
  }

} else {
// unpadded lines
fread( ushortpColorArray, 1, iImageBytes, sFileHandle );
}

//
// build BGR array
//
unsigned char *ucpImageArray = (unsigned char *) malloc( iNumPixels * 3 );

for( i=0; i<iNumPixels; i++ ) {
for( j=0; j<3; j++ ) {
ucpImageArray[ i*3+j ] = (ushortpColorArray[i]&uintColorMasks[j])>>iColorShifts[j];
}
}

//
// free up and return
//

free( ushortpColorArray );

return ucpImageArray;
}

unsigned char *CImage::LoadBMP32bppBIT( FILE *sFileHandle, BITMAPINFOHEADER *spInfoHeader )
{
int i,j;

//
// read and process the three color masks
//

unsigned int uintColorMasks[3];
int iColorShifts[3];

uintColorMasks[0] = file_read_uint( sFileHandle );
uintColorMasks[1] = file_read_uint( sFileHandle );
uintColorMasks[2] = file_read_uint( sFileHandle );

for( i=0; i<3; i++ ) {
for( j=0; !((uintColorMasks[i]>>j) & 1) && j<32; j++ );
iColorShifts[i]=j;
}

//
// Skip Palette if necessary
//

int iNumPaletteEntries = spInfoHeader->biClrUsed;
fseek( sFileHandle, iNumPaletteEntries * sizeof( RGBQUAD ), SEEK_CUR );

//
// read index array from bitmap file
//

int iNumPixels = spInfoHeader->biWidth * spInfoHeader->biHeight;
int iReadBytes = iNumPixels*4;
unsigned int uintpColorArray = (unsigned int ) malloc( iReadBytes );

fread( uintpColorArray, 1, iReadBytes, sFileHandle );

//
// build BGR array
//
unsigned char *ucpImageArray = (unsigned char *) malloc( iNumPixels * 3 );

for( i=0; i<iNumPixels; i++ ) {
for( j=0; j<3; j++ ) {
ucpImageArray[ i*3+j ] = (uintpColorArray[i]&uintColorMasks[j])>>iColorShifts[j];
}
}

//
// free up and return
//

free( uintpColorArray );

return ucpImageArray;
}

[This message has been edited by Michael Steinberg (edited 06-27-2002).]

[This message has been edited by Michael Steinberg (edited 06-27-2002).]

Originally posted by Michael Steinberg:
A 16 bit BMP stores a single pixel in one 32 bit DWORD?
A 16 bit BMP has 16 bits of color information per pixel. They are encode in 5,5,5 scheme, with 5 bits per color component.

Oops, my mistake, meant to say 16 bit per pixel, not 32 bit

To say it this way, the color component masks are stored as three DWORDS no matter whether the image is 16 or 32 bpp.

Originally posted by Michael Steinberg:
They are encode in 5,5,5 scheme, with 5 bits per color component.

Its actually common to see 5,6,5 bitmaps as well as 5,5,5 so your loader should handle both.

Yeah, my fault, didnt check the specs well. However i implemented BITFIELDS thingy, so the code actually handled both without real intention. I should’ve meant that 95/9X dont support arbitrary masks.

Do you mean that GDI doesn’t handle arbitrary color masks in Win9x?

Thanks for the help guys!

But I didn’t understand yet.

I understood that a 32-bit bitmap, with biCompression = BI_RGB, has no COLOR TABLE (palette), and the pixels are stored as a DWORD (4 bytes = R, G, B, A).

What I didn’t figured out yet, is what I have to do if biCompression has a value of BI_BITFIELD. I know that in this mode, the pixels are still represented by DWORDs, but I don’t know what I do with the 3 colors in the pallete.

Thank you
Fernando

jra101, exactly.
Fernando, all of the functions i posted are only about the BITFIELDS format.
There are no colors store in the COLORTABLE then, there are 3 DWORDs, each one a mask for a color-component of the pixeldata (in the order BGR). Then, for every pixel-WORD/DWORD (16/32bpp that is) you AND the mask and the WORD/DWORD and shift the bits to the right until the rightmost bit is 1. That is exactly what my code does. The bits of a color must be consecutive, but the bits of all colors together must not be 16 or 32 bits respectively. To get values from 0…1 for the color components, you must then divide the color-component by the maximum the mask bits for the color-component can represent. Which is 2^num_bits_of_color_component_mask.
Well, a little bit hard to explain.

And you can see that my code actually is wrong, because the resulting color component additionaly would have to be scaled between 0…255. Gonna fix that now…

[This message has been edited by Michael Steinberg (edited 06-28-2002).]

Windows 95 and Windows 98: When the biCompression member is BI_BITFIELDS, the system supports only the following 16bpp color masks: A 5-5-5 16-bit image, where the blue mask is 0x001F, the green mask is 0x03E0, and the red mask is 0x7C00; and a 5-6-5 16-bit image, where the blue mask is 0x001F, the green mask is 0x07E0, and the red mask is 0xF800.

Windows 95 and Windows 98: When the biCompression member is BI_BITFIELDS, the system supports only the following 32bpp color mask: The blue mask is 0x000000FF, the green mask is 0x0000FF00, and the red mask is 0x00FF0000.

Thank you Michael!

I read your post (the firsts) again, and now, I think i figured out.

Look if it is right:

For 16-bit images, if, we have for example:

var = 1101100110111011 = 55739

we have to separate:
11011 001101 11011

We separate them by this way:
B = (var & 1111100000000000b);
G = (var & 11111100000b);
R = (var & 11111b);

I think it’s correct. But now, as you say, we have to scale to 255… How do we do that?

Can be this way?:
B = B * 8;
G = G * 4;
R = R * 8;

I arrived to this conclusion because with 5 bits (used in B and R), we can get only numbers up to 32, and with 6 bits (used in G), we can get numbers up to 64.

255 / 32 = 8
255 / 64 = 4

Is this correct? Thank you again!

Fernando

Okay, the specs say that the bits in the mask must be contiguous.
So, first we must count how many bits the masked pixel data must be shifted to the right. This does exactly that and stores the result in an array:

for( i=0; i<3; i++ ) {
for( j=0; !((uintColorMasks[i]>>j) & 1) && j<32; j++ );
iColorShifts[i]=j;
}

i iterates through the 3 masks, and j rightshifts the mask until the first bit is set (true).

After that, we need to count how many bits the mask contains. As the (rightshifted) mask in itself is the biggest number the color-component can have, the number of representable numbers is

(uintColorMasks[j] >> iColorShifts[j])+1;

So to get the correct color component from 0…1, you just have to do this:

float component = ((ushortpColorArray[i]&uintColorMasks[j])>>iColorShifts[j]) / ((uintColorMasks[j] >> iColorShifts[j])+1);

Some of the equation of course is better precomputed, but I wrote it here and didnt just copy it.

We separate them by this way:
B = (var & 1111100000000000b);
G = (var & 11111100000b);
R = (var & 11111b);

Now you’ve seperated them, but B and G are huge numbers. You need to rightshift them.

B = (var & 1111100000000000b);
the mask has 11 zeros at the end. So you need to rightshift the value 11 times (or one time by 11 bits).
( var & 1111100000000000b) >> 11;

This is exactly what im doing in my code, only that my code actually counts the zeros at the end, because the the BMP format says the masks can be arbitrary, so we can’t be sure how many 0’s will be at the end.

Thank you a lot Michael! :slight_smile:
You helped me a lot! The 16-bit mode is working now (with biCompression = BI_RGB).

Now, i’m goint to test opening a bitmap that has biCompression equal to BI_BITFIELDS.

Do you know a program who save bitmaps in this kind of format? I have Paint Shop here, but it doesn’t have an option to 16-Bit mode.

Thank you again.

Fernando