§ ¶The BI_BITFIELDS bitmap type
Everyone knows how to deal with bitmaps in Windows, right? The ones that are described by BITMAPINFOHEADER?
Enter a seldom-used bitmap mode called BI_BITFIELDS.
Most bitmaps in Windows have their biCompression field set to BI_RGB, which just indicates a straight indexed or direct-mapped RGB encoding. This encompasses indexed color formats of 2, 4, 16, or 256 colors, as well as 16-bit, 24-bit, and 32-bit direct color encodings. That handles virtually everything that you'll ever encounter, except for one gotcha. People who are new to BITMAPINFOHEADER often screw up in handling the 16-bit format, because they don't realize that it is actually 15 bits -- five bits of red/green/blue each, and one unused high bit. This is known as a 555 or 1555 encoding. Despite this format having only 15 significant bits, it still uses a bit depth value of 16, which is what confuses newbies.
Windows does actually support a true 16-bit encoding, which consists of five bits of red and blue each and six bits of green, or a 565 encoding. You can't get to it via BI_RGB, however; you have to use the special BI_BITFIELDS compression value instead. This adds three bit masks after the biClrImportant field that specify the exact bit locations of the red, green, and blue fields. When these bit masks are 0000F800, 000007E0, and 0000001F, respectively, the bitmap uses a 565 16-bit encoding. Windows GDI supports this format because a lot of popular graphics hardware used to support only a particular 16-bit frame buffer format and 565 was one of the common ones.
The reason I bring this up is that I recently started rewriting the part of VirtualDub that handles BI_BITFIELDS images as part of some other work. You might be wondering if anyone uses BI_BITFIELDS encoding, especially in AVI files, and the answer is that it's extremely rare. However, one thing I've learned from working on this program is that almost anything is possible will eventually show up in the wild; for instance, I once had to fix a crash caused by trying to import images produced by a popular SNES emulator that used BITMAPCOREHEADER instead of BITMAPINFOHEADER in the format block. Anyway, a particularly interesting way you can get BI_BITFIELDS in a video stream is using GraphEdit to decompress a video from a decompression filter that supports 565 for display performance issues and happens to have it listed first in the format list of the output pin. Therefore, it's one of the things I've been regression testing.
...Only to discover that most other programs don't handle BI_BITFIELDS properly to begin with.
It turns out that there are only four direct color formats that Windows GDI is guaranteed to handle: 16-bit (555), 16-bit (565), 24-bit (888), and 32-bit (888). Win9x OSes will only handle BI_BITFIELDS images with these four encodings. Of these, three of them are accessible via BI_RGB, with 565 being the only one that absolutely requires the bitfield encoding. Presumably for this reason, it seems that just about every video technology I have installed was written with the assumption that BI_BITFIELDS always means 565, including core components of Microsoft DirectShow like the Color Space Converter. If you use BI_BITFIELDS with the 555 masks (00007C00 / 000003E0 / 0000001F), the video player still tries to decode it as 565 and displays garbage. Hey guys, know those other fields in the bitmap structure? You're supposed to check them!
When things get really fun, however, is when you take into account that Windows NT GDI is considerably more powerful and will accept arbitrary non-overlapping, contiguous bit masks. Current versions of VirtualDub fall back to GDI's BitBlt() when encountering a BI_BITFIELDS video stream, and thus if you are running on Windows NT4/2000/XP/Vista, you'll be able to open most BI_BITFIELDS encoded videos. I wrote a test program to convert a BMP image sequence to BI_BITFIELDS encoded AVI files with user-specified bit masks, and GDI handled many other popular formats through BI_BITFIELDS, such as BGR 555 and RGB 444, and even 2/10/10/10. I say almost, because GDI doesn't seem to like it if you use a zero bit mask for a channel, although interestingly it doesn't mind if you use 0x10000 in a 16-bit format. Ultimately useless, but I list it here for completeness and since it was trivial to get it working in the rewritten bitfields code.
I didn't have much of a point in writing this other than sharing what I'd found during testing, but if you were looking for a conclusion here, I'd say: make sure you validate the bit masks when processing BI_BITFIELDS, and if you write bitmaps or video with that format, only do so for 565 and otherwise use BI_RGB whenever possible.
I find working with BI_BITFIELDS can be a hair-pulling experience - sometimes.
I was working on an app that was using VFW API services to render frames to a 16bpp surface (using SDL). I needed 16bpp rendering since the surface was to be copied to external hardware that only supported RGB 565 format, and I thought that if I could get the decompressor to render direct to 16bpp it would be faster than having it render a 32bpp DIB and then having me transcode the 32bpp data to 16bpp.
Setting up the decompressor (using AVI streams) I noticed that if I used:
pGetFrame = AVIStreamGetFrameOpen( pVideoStream, (LPBITMAPINFOHEADER)AVIGETFRAMEF_BESTDISPLAYFMT );
when my display was in 16bpp (565) format, I could get the decompressor to render correct 16bpp 565 DIBS.
So initially, I tried to set it up using the 3 MASK bitfields that follow the BMI structure. This didn't work. I have a feeling that the VFW API are so incredibly old that they supported 16bpp before the added bitfields masks were added to the bitmap spec.
Anyways, I ended up doing this:
memset( &BMI, 0, sizeof(BMI) );
BMI.biSize = sizeof(BMI);
BMI.biWidth = VideoStreamFormat.biWidth;
BMI.biHeight = VideoStreamFormat.biHeight;
BMI.biPlanes = 1;
BMI.biBitCount = 16;
BMI.biCompression = BI_BITFIELDS;
BMI.biSizeImage = BMI.biWidth*BMI.biHeight*2;
pGetFrame = AVIStreamGetFrameOpen( pVideoStream, &BMI );
If I create the stream this way, later call like this:
BITMAPINFOHEADER* pDIB = (BITMAPINFOHEADER*)AVIStreamGetFrame( pGetFrame, iFrameToRead );
will return me a 16bpp 565 DIB.
What's important is that I do NOT need the appended RGB bitfield masks in BMI when I call AVIStreamGetFrameOpen() and that setting BMI.biSizeImage is required for it to work.
I suspect that this is one of the very few situations where you can use BI_BITFIELDS to get 16bpp 565 data WITHOUT having to append the RGB bitfield masks - probably because old displays didn't using 15bpp 555 mode and MS needed a way for the VFW decompressor to render directly to a display's 16bpp 565 format.
Also, my benchmark tests showed that it was slightly faster to have the decompressor render to 16bpp than to have it render to 32bpp and then me use C++ code to recode the DIB to 16bpp.
BitBasher - 26 11 07 - 09:06
Here's the flaw in your reasoning: Nowhere in the APIs that you called do you specify the size of the bitmap structure passed. The calls only take a pointer to the structure. Therefore, VFW doesn't know whether you've actually included the bit masks or not, and if it looked for them, it would have read random data. Did you, by any chance, set biSize to include the bit masks? That would have caused problems, because biSize excludes the bit masks when BI_BITFIELDS is used with BITMAPINFOHEADER. It does include them with BITMAPV4HEADER or BITMAPV5HEADER, but that's because those structures have fields for the masks.
I should also point out that largely VFW doesn't interpret bitmap structures, but instead passes them off verbatim to whatever codecs are used. You're much more at the mercy of the bitmap parsing code in the codec. There is, however, one place in VFW that definitely affects you, and that's in the packed bitmap that AVIStreamGetFrame() returns -- VFW would have to compute the bitmap structure size itself in order to hand off the data pointer to the decompressing codec. I could see that being broken for BI_BITFIELDS. No API exists to compute the total size of a bitmap structure, and there are so many corner cases that I don't think anyone gets it completely right.
Phaeron - 27 11 07 - 00:35
Hmmm, yes my initial reasoning may be flawed (I didn't disassemble the AVI API functions to see what's really going on).
I think the point I was trying to make is that what I finally did WORKED, and I did NOT need to add the RGB bitmask fields - that was the INTERESTING point. Also, if I leave biSizeImage zero then it will fail - it had to be setup for it to work (but it doesn't seem to need to be setup for 32bpp).
I did try MANY different things including what you suggested (changing biSize). I even tried using the newer BITMAPINFO structures. In the end, what I ended up coded seemed to work*.
*I agree that I'm probably at the mercy of the bitmap code within the codec - and there's probably a good chance that some codecs might go boom. For my needs, I only needed to decode DIVX - which worked fine. If a codec did go boom, I wonder if that coded would work using AVIGETFRAMEF_BESTDISPLAYFMT on a 16bpp (565) display? Hmmm.
Thanks for the feedback! I'm always trying to get things working right! :D
BitBasher - 27 11 07 - 07:52
Please keep comments on-topic for this entry.
If you have unrelated comments about VirtualDub, the forum is a better place to post them.