Win32 Series - The DIB File Format

http://www-user.tu-chemnitz.de/~heha/petzold/

 

 

The DIB File Format

Interestingly enough, the DIB format did not originate in Windows. It was first defined  in version 1.1 of OS/2, the operating system originally developed by IBM and  Microsoft beginning in the mid-1980s. OS/2 1.1 was released in 1988 and was the first version of  OS/2 to include a Windows-like graphical user interface, known as the Presentation  Manager (PM). The Presentation Manager included the Graphics Programming Interface (GPI),  which defined the bitmap format. 

That OS/2 bitmap format was then used in Windows 3.0 (released in 1990), where  it came to be known as the DIB. Windows 3.0 also included a variation of the original  DIB format that under Windows has come to be the standard. Additional enhancements  were defined in Windows 95 (and Windows NT 4.0) and Windows 98 (and Windows NT  5.0), as I'll discuss in this chapter.

The DIB is best examined first as a file format. DIB files have the filename  extension .BMP or, more rarely, .DIB. Bitmap images used by Windows applications (for  example, on the surfaces of buttons) are created as DIB files and generally stored as read-only  resources in the program's executable file. Icons and mouse cursors are also DIB files in  a slightly different form. 

A program can load a DIB file, minus the first 14 bytes, into a contiguous block  of memory. It is then sometimes referred to as "a bitmap in the packed-DIB format." Applications running under Windows can use the packed-DIB format for exchanging  images through the Windows clipboard or for creating brushes. Programs also have complete  access to the contents of the DIB and can modify the DIB in whatever way they choose. 

Programs can also create their own DIBs in memory and later save them in files.  The images in these DIBs can be "painted" by a program using GDI function calls. Or  the program can set and manipulate the pixel bits directly, perhaps using other  memory-based DIBs in the process.

When a DIB is loaded into memory, programs can also use the DIB data with  several Windows API function calls, that I'll discuss in this chapter. The DIB-related  API calls are few in number and are mainly concerned with displaying display DIBs on the  video display or a printer page and with converting them to and from GDI bitmap objects.

When all is said and done, however, there remain many, many, many DIB tasks  that application programs might need to perform for which there is no support in the  Windows operating system. For example, a program might have access to a 24-bit DIB and  might wish to convert it into an 8-bit DIB with an optimal 256-color palette. Windows will  not do this for you. But this chapter and the next will show you how to work with DIBs  beyond what the Windows API provides.

The OS/2-Style DIB

So that we don't get bogged down in too many details just yet, let's take a look at the  format of the Windows DIB that is compatible with the bitmap format first introduced in OS/2 1.1. 

A DIB file has four main sections:

  • A file header

  • An information header

  • An RGB color table (but not always)

  • The bitmap pixel bits

You can think of the first two parts as C data structures and the third part as an  array of data structures. These structures are documented in the Windows header file  WINGDI.H. A memory-based DIB in the packed-DIB format has three sections:

  • An information header

  • An RGB color table (but not always)

  • The bitmap pixel bits

It's exactly the same as a DIB stored in a file except that it doesn't have the file header.

The DIB file, but not the memory-based packed DIB, begins with a 14-byte file  header defined as a structure like so:

typedef struct tagBITMAPFILEHEADER  // bmfh
{ 
     WORD  bfType ;        // signature word "BM" or 0x4D42
     DWORD bfSize ;        // entire size of file
     WORD  bfReserved1 ;   // must be zero
     WORD  bfReserved2 ;   // must be zero
     DWORD bfOffsetBits ;  // offset in file of DIB pixel bits
}
BITMAPFILEHEADER, * PBITMAPFILEHEADER ;

This may not be exactly the way the structure is defined in WINGDI.H (for example,  the comments are mine), but it is functionally the same. The first comment (that is, the  text "bmfh") shows the recommended abbreviation when naming a structure variable of  this data type. If you see a variable in one of my programs named  pbmfh, that will be a pointer to a structure of type BITMAPFILEHEADER or a variable of type PBITMAPFILEHEADER. 

The structure is 14 bytes in length. It begins with the two letters "BM" to indicate  a bitmap file. This is the WORD value 0x4D42. The "BM" indicator is followed by a  DWORD indicating the entire size of the file, including the file header, in bytes. The next  two WORD fields are set to zero. (In a mouse cursor file, which is similar in format to a  DIB file, these two fields are used to indicate the "hot spot" of the cursor.) The structure  concludes with a DWORD indicating the byte offset within the file where the pixel bits  begin. This number can be derived from information in the DIB information header, but it  is provided here for convenience.

In the OS/2-style DIB, the BITMAPFILEHEADER structure is followed  immediately by a BITMAPCOREHEADER structure, which provides the basic information about the  DIB image. A packed DIB begins with the BITMAPCOREHEADER:

typedef struct tagBITMAPCOREHEADER  // bmch
{ 
     DWORD bcSize ;      // size of the structure = 12
     WORD  bcWidth ;     // width of image in pixels
     WORD  bcHeight ;    // height of image in pixels
     WORD  bcPlanes ;    // = 1
     WORD  bcBitCount ;  // bits per pixel (1, 4, 8, or 24)
}
BITMAPCOREHEADER, * PBITMAPCOREHEADER ;

The word "core" sounds a little odd in this context, and it is. It means that this format  is the basis (thus the core) of other bitmap formats derived from it.

The bcSize field in the BITMAPCOREHEADER structure indicates the size of the  data structure, in this case 12 bytes.

The bcWidth and bcHeight fields contain the size of the bitmap in pixels.  Although the use of a WORD for these fields implies that a DIB may be 65,535 pixels high and  wide, you'll rarely encounter anything quite that large. 

The bcPlanes field is always 1. Always, always, always—from the time it was  defined until this very second. The field is a remnant of the earlier Windows GDI bitmap  object that we encountered in the last chapter. 

The bcBitCount field indicates the number of bits per pixel. For OS/2-style DIBs,  this can be either 1, 4, 8, or 24. The number of colors in the DIB image is equal to  2bmch.bcBitCountor, in C syntax, to

1 << bmch.bcBitCount

Thus, the bcBitCount field is equal to:

  • 1 for a 2-color DIB

  • 4 for a 16-color DIB

  • 8 for a 256-color DIB

  • 24 for a full-color DIB

When I refer to "an 8-bit DIB," I'll mean a DIB that has 8 bits per pixel.

For the first three cases (that is, for bit counts of 1, 4, and 8), the  BITMAPCOREHEADER is followed by the color table. The color table does not exist for 24-bit DIBs.  The color table is an array of 3-byte RGBTRIPLE structures, one for each color in the image:

typedef struct tagRGBTRIPLE  // rgbt
{
     BYTE rgbtBlue ;   // blue level
     BYTE rgbtGreen ;  // green level
     BYTE rgbtRed ;    // red level
}
RGBTRIPLE ;

It is recommended that the color table be arranged so that the most important colors  in the DIB appear first. We'll see why in the next chapter.

The WINGDI.H header file also defines the following structure:

typedef struct tagBITMAPCOREINFO  // bmci
{
     BITMAPCOREHEADER bmciHeader ;     // core-header structure
     RGBTRIPLE        bmciColors[1] ;  // color table array
}
BITMAPCOREINFO, * PBITMAPCOREINFO ;

This structure combines the information header with the color table. Although the  number of RGBTRIPLE structures is seemingly equal to 1 in this structure, you'll never find just  one RGBTRIPLE in a DIB file. The size of the color table is always 2, 16, or 256  RGBTRIPLE structures, depending on the number of bits per pixel. If you need to allocate a  structure of PBITMAPCOREINFO for an 8-bit DIB, you can do it like so:

pbmci = malloc (sizeof (BITMAPCOREINFO) + 255 * sizeof (RGBTRIPLE)) ;

Then you can access whatever RGBTRIPLE structure you need like so:

pbmci->bmciColors[i]

Because the RGBTRIPLE structure is 3 bytes in length, some of the RGBTRIPLE  structures might begin at odd addresses within the DIB. However, because there are always  an even number of RGBTRIPLE structures in the DIB file, the data block that follows the  color table array always begins at a WORD address boundary.

The data that follow the color table (and what follows the information header for  DIBs with a bit count of 24) are the pixel bits themselves.

Bottoms Up!

Like most bitmap formats, the pixel bits in the DIB are organized in horizontal rows,  sometimes also called "scan lines" from the terminology of video display hardware. The  number of rows is equal to thebcHeight field of the BITMAPCOREHEADER structure.  However, unlike most bitmap formats, the DIB begins with the bottom row of the image and  proceeds up through the image.

Let's establish some terminology here. When I say "top row" and "bottom row," I  mean the top and bottom of the visual image as it appears when correctly displayed on  the monitor or printer page. The top row of a portrait is hair; the bottom row of a portrait  is chin. When I say "first row," I mean the row of pixels that is found directly after the  color table in the DIB file. And when I say "last row," I mean the row of pixels at the very  end of the file.

So, in DIBs, the bottom row of the image is the first row of the file, and the top  row of the image is the last row in the file. This is called a bottom-up organization.  Because this organization is counterintuitive, you may ask why it's done this way.

Well, it all goes back to the OS/2 Presentation Manager. Someone at IBM decided  that all coordinate systems in PM—including those for windows, graphics, and  bitmaps—should be consistent. This provoked a debate: Most people, including programmers who  have worked with full-screen text programming or windowing environments, think in terms  of vertical coordinates that increase going down the screen. However, hardcore  computer graphics programmers approach the video display from a perspective that originates in  the mathematics of analytic geometry. This involves a rectangular (or Cartesian)  coordinate system where increasing vertical coordinates go up in space.

In short, the mathematicians won. Everything in PM was saddled with a  bottom-left origin, including window coordinates. And that's how DIBs came to be this way.

The DIB Pixel Bits

The last section of the DIB file—in most cases the great bulk of the DIB file—consists  of the actual DIB pixel bits. The pixel bits are organized in horizontal rows beginning  with the bottom row of the image and proceeding up through the image.

The number of rows in a DIB is equal to the  bcHeight field of the BITMAPCOREHEADER structure. Each row encodes a number of pixels equal to the bcWidth field of the structure. Each row begins with the leftmost pixels and proceeds to the right of the  image. The number of bits per pixel is obtained from the bcBitCount field, which is either 1, 4, 8, or 24.

The length of each row in bytes is always a multiple of 4. The row length can  be calculated like so:

RowLength = 4 * ((bmch.bcWidth * bmch.bcBitCount + 31) / 32) ;

Or, slightly more efficiently in C, like this:

RowLength = ((bmch.bcWidth * bmch.bcBitCount + 31) & ~31) >> 3 ;

The row is padded at the right (customarily with zeros), if necessary, to achieve  this length. The total number of bytes of pixel data is equal to the product of RowLength andbmch.bcHeight.

To see how the pixels are encoded, let's examine the four cases separately. In  the diagrams shown below, the bits of each byte are shown in boxes and are numbered  with 7 indicating the most-significant bit and 0 indicating the least-significant bit. Pixels are  also numbered beginning with 0 for the leftmost pixel in the row.

For DIBs with 1 bit per pixel, each byte corresponds to 8 pixels. The leftmost  pixel is the most-significant bit of the first byte:

click here to view full size

Each pixel can be either a 0 or a 1. A 0 bit means that the color of that pixel is given  by the first RGBTRIPLE entry in the color table. A 1 bit is a pixel whose color is the  second entry of the color table.

For DIBs with 4 bits per pixel, each byte corresponds to 2 pixels. The leftmost  pixel is the high 4 bits of the first byte, and so on:

click here to view full size

The value of each 4-bit pixel ranges from 0 to 15. This value is an index into the 16  entries in the color table.

For a DIB with 8 bits per pixel, each byte is 1 pixel:

click here to view full size

The value of the byte is 0 through 255. Again, this is an index into the 256 entries in  the color table.

For DIBs with 24 bits-per-pixel, each pixel requires 3 bytes for the red, green,  and blue color values. Each row of pixel bits is basically an array of RGBTRIPLE  structures, possibly padded with 0 bytes at the end of each row so that the row has a multiple of 4 bytes:

click here to view full size

Again, the 24-bit-per-pixel DIB has no color table.

The Expanded Windows DIB

Now that we've mastered the OS/2-compatible DIB introduced in Windows 3.0, we  can take a look at the expanded version of the DIB introduced in Windows at the same time.

This form of the DIB begins with a BITMAPFILEHEADER structure just like the  earlier format but then continues with a BITMAPINFOHEADER structure rather than a  BITMAPCOREHEADER structure:

typedef struct tagBITMAPINFOHEADER  // bmih
{
     DWORD biSize ;           // size of the structure = 40
     LONG  biWidth ;          // width of the image in pixels
     LONG  biHeight ;         // height of the image in pixels
     WORD  biPlanes ;         // = 1
     WORD  biBitCount ;       // bits per pixel (1, 4, 8, 16, 24, or 32)
     DWORD biCompression ;    // compression code
     DWORD biSizeImage ;      // number of bytes in image
     LONG  biXPelsPerMeter ;  // horizontal resolution
     LONG  biYPelsPerMeter ;  // vertical resolution
     DWORD biClrUsed ;        // number of colors used
     DWORD biClrImportant ;   // number of important colors
}
BITMAPINFOHEADER, * PBITMAPINFOHEADER ;

You can distinguish an OS/2-compatible DIB from a Windows DIB by checking  the first field of the structure, which is 12 in the former case and 40 in the latter case. 

As you'll note, there are six additional fields in this structure, but the  BITMAPINFOHEADER structure is not simply a BITMAPCOREHEADER with some new stuff tacked  on to the end. Take a closer look: In the BITMAPCOREHEADER structure, the bcWidth and bcHeight fields are 16-bit WORD values. In this structure, they are 32-bit LONG values.  This is an annoying little change that is guaranteed to drive you nuts.

Another change: For 1-bit, 4-bit, and 8-bit DIBs using the BITMAPINFOHEADER  structure, the color table is not an array of RGBTRIPLE structures. Instead, the  BITMAPINFOHEADER structure is followed by an array of RGBQUAD structures:

typedef struct tagRGBQUAD  // rgb
{
     BYTE rgbBlue ;      // blue level
     BYTE rgbGreen ;     // green level
     BYTE rgbRed ;       // red level
     BYTE rgbReserved ;  // = 0 
}
RGBQUAD ;

This is the same as the RGBTRIPLE structure except that it includes a fourth field that  is always set to 0. The WINGDI.H header file also defines the following structure:

typedef struct tagBITMAPINFO  // bmi
{
     BITMAPINFOHEADER bmiHeader ;      // info-header structure
     RGBQUAD          bmiColors[1] ;   // color table array
}
BITMAPINFO, * PBITMAPINFO ;

Note that if the BITMAPINFO structure begins at a 32-bit address boundary, each entry  in the RGBQUAD array also begins at a 32-bit address boundary because the  BITMAPINFOHEADER structure is 40 bytes in length. This assures more efficient addressing of the  color table data by 32-bit microprocessors.

Although the BITMAPINFOHEADER was originally defined for Windows 3.0,  some of the fields were redefined in Windows 95 and Windows NT 4.0, and these have  been carried over into Windows 98 and Windows NT 5.0. For example, the current  documentation states: "If biHeight is negative, the bitmap is a top-down DIB and its origin is  the upper left corner." That's good to know. It would be even better if somebody had  made this decision in 1990 when this DIB format was originally defined. My advice is to  avoid creating top-down DIBs. You're almost begging that some program written without  awareness of this new "feature" will crash upon encountering a negative biHeight field. Or that programs such as the Microsoft Photo Editor included with Microsoft Word 97 will  report "Illegal image height" upon encountering a top-down DIB (although Word 97 itself does  fine with them).

The biPlanes field is still always 1, but the  biBitCount field can now be 16 or 32 as well as 1, 4, 8, or 24. This was also a new feature in Windows 95 and Windows NT 4.0.  I'll discuss how these additional formats work shortly.

Let me skip the biCompression and  biSizeImage fields for now. I'll also  discuss them shortly.

The biXPelsPerMeter and  biYPelsPerMeter fields indicate a suggested real-world  size of the image in the ungainly units of pixels per meter. (The "pel"—picture element—is  what IBM liked to call the pixel.) Internally, Windows does not use this information.  However, an application could use it to display a DIB in an accurate size. These fields are also  useful if the DIB originated from a device that does not have square pixels. In most DIBs,  these fields are set to 0, which indicates no suggested real-world size. A resolution of 72  dots per inch (which is sometimes used for video displays, although the actual resolution  depends on the size of the monitor) is approximately equivalent to 2,835 pixels per  meter, and a common printer resolution of 300-dpi is 11,811 pixels per meter.

The biClrUsed field is a very important field because it affects the number of  entries in the color table. For 4-bit and 8-bit DIBs, it can indicate that the color table contains  fewer than 16 or 256 entries, respectively. This is one method to shrink down the size of the  DIB, although not by very much. For example, suppose a DIB image contains only 64  gray shades. ThebiClrUsed field is set to 64, and the color table contains 64 RGBQUAD  structures for a total color table size of 256 bytes. The pixel values then range from 0x00  through 0x3F. The DIB still requires 1 byte per pixel, but the high 2 bits of each pixel byte are  zero. If thebiClrUsed field is set to 0, it means that the color table contains the full number  of entries implied by thebiBitCount field.

Beginning with Windows 95, the biClrUsed field can be nonzero for 16-bit,  24-bit, or 32-bit DIBs. In these cases, the color table is not used by Windows to interpret the  pixel bits. Instead, it indicates the size of a color table in the DIB that could be used by  programs to set a palette to display the DIB on 256-color video displays. You'll recall that  in the OS/2-compatible format, a 24-bit DIB had no color table. This was also true of the  extended format introduced in Windows 3.0. The change in Windows 95 means that a  24-bit DIB can have a color table the size of which is indicated by the biClrUsed field.

To summarize:

  • For 1-bit DIBs, biClrUsed is always 0 or 2. The color table always has 2 entries.

  • For 4-bit DIBs, if the biClrUsed field is 0 or 16, the color table has 16 entries.  If it's a number from 2 through 15, it indicates the number of entries in the  color table. The maximum value of each pixel is 1 less than this number.

  • For 8-bit DIBs, if the biClrUsed field is 0 or 256, the color table has 256  entries. If it's a number from 2 through 255, it indicates the number of entries in  the color table. The maximum value of each pixel is 1 less than this number.

  • For 16-bit, 24-bit, and 32-bit DIBs, the  biClrUsed field is usually 0. If it's not 0, it indicates the number of entries in the color table. These entries could  be used by an application running with a 256-color video adapter to set a  palette for the DIB.

Another warning: Programs originally written using the earlier DIB  documentation do not expect to see a color table in 24-bit DIBs. You put one in at your own risk.

Despite its name, the biClrImportant field is actually much less important than  thebiClrUsed field. It's usually set to 0 to indicate that all colors in the color table are  important, or it could be set to the same value as biClrUsed. Both mean the same thing. If it's  set somewhere in between 0 andbiClrUsed, it means that the DIB image can be  reasonably rendered using only the first biClrImportant entries in the color table. This could be  useful when displaying two or more 8-bit DIBs side by side on a 256-color video adapter.

For 1-bit, 4-bit, 8-bit, and 24-bit DIBs, the organization of the pixel bits is the  same as in the OS/2-compatible DIB. I'll discuss the 16-bit and 32-bit DIBs shortly.

Reality Check

What can you expect to find when you encounter a DIB that was created by some  other program or person?

Although OS/2-style DIBs were common when Windows 3.0 was first released,  they have become quite scarce in recent years. Some programmers writing quickie DIB  routines virtually ignore them. Any 4-bit DIBs you'll encounter will probably have been created  in the Windows Paint program using a 16-color video display. The color table will have  the standard 16 colors on these displays.

Probably the most common DIBs you'll find will have a bit count of 8. The 8-bit  DIBs will fall into two categories: gray-shade DIBs and palletized color DIBs.  Unfortunately, nothing in the header indicates what type of 8-bit DIB you're dealing with.

Some gray-shade DIBs will have a  biClrUsed field equal to 64, indicating 64  entries in the color table. These entries will usually be in order of ascending levels of gray. That  is, the color table will begin with RGB values of 00-00-00, 04-04-04, 08-08-08, 0C-0C-0C,  and conclude with RGB values of F0-F0-F0, F4-F4-F4, F8-F8-F8, and FC-FC-FC. Such a color  table is calculated using a formula something like

rgb[i].rgbRed = rgb[i].rgbGreen = rgb[i].rgbBlue = i * 256 / 64 ;

where rgb is an array of RGBQUAD structures and  i ranges from 0 through 63. Or the gray-shade color table will have been calculated with a formula that looks like

rgb[i].rgbRed = rgb[i].rgbGreen = rgb[i].rgbBlue = i * 255 / 63 ;

so that the table ends with FF-FF-FF.

It really doesn't matter which formula is used. Many video display adapters  and monitors don't have a color precision greater than 6 bits anyway. The first formula  recognizes that fact; the second formula, however, is more appropriate when generating  fewer than 64 gray shades, perhaps 16 or 32 (in which case the divisor at the end of the  formula is 15 or 31, respectively), because it ensures that the last entry in the color table is  FF-FF-FF, which is white.

While some 8-bit gray-shade DIBs have 64 entries in the color table, other  gray-shade DIBs have 256 entries. The biClrUsed field can actually be 0 (indicating 256 entries in  the color table) or anything from 2 through 256. Of course, it doesn't make much sense to  have abiClrUsed value of 2 (because such an 8-bit DIB could be re-coded as a 1-bit DIB)  or a value less than or equal to 16 (because that could be re-coded as a 4-bit DIB),  but it could be done. Whatever the case, the number of entries in the color table must be the  same as the biClrUsed field (or 256 if biClrUsed is 0), and the pixel values cannot  number of color table entries minus 1. That's because the pixel values are indices into  the color table array. For 8-bit DIBs with a biClrUsed value of 64, the pixel values range  from 0x00 to 0x3F.

Here's the important thing to remember: When an 8-bit DIB has a color table  consisting entirely of gray shades (that is, when the red, green, and blue levels are equal),  and when these gray-shade levels uniformly increase in the color table (as I described  above), then the pixel values themselves represent proportional levels of gray. That is, if biClrUsed is 64, then a pixel value of 0x00 is black, a pixel value of 0x20 is 50 percent gray, and  a pixel value of 0x3F is white.

This can be important for some image-processing tasks because you can ignore  the color table entirely and deal solely with the pixel values. This is so useful that if I  were allowed to go back in time and make a single change to the BITMAPINFOHEADER  structure, I'd add a flag to indicate that the DIB image is gray-shaded, the DIB has no color  table, and the pixel values directly indicate the gray level.

Palletized 8-bit color DIBs will generally use the whole color table and thus have  abiClrUsed field of 0 or 256. However, you'll also encounter some that have a smaller  number of colors—for example, 236. This is in recognition of the fact that programs  usually can change only 236 entries in the Windows color palette to display these DIBs  accurately, as I'll discuss in the next chapter.

Encountering nonzero values of  biXPelsPerMeter and biYPelsPerMeter will be  rare. Also rare will be encountering abiClrImportant field that is something other than 0 or the value of biClrUsed.

DIB Compression

Earlier I delayed discussion of the  biCompression and biSizeImage fields in the  BITMAPINFOHEADER. Now's the time to examine these values.

The biCompression field can be one of four constants—BI_RGB, BI_RLE8,  BI_RLE4, or BI_BITFIELDS—defined in the WINGDI.H header file as the values 0 through 3,  respectively. This field serves two purposes: For 4-bit and 8-bit DIBs, it indicates that the  pixel bits have been compressed using a type of run-length encoding. For 16-bit and 32-bit  DIBs, it indicates whether color masking has been used to encode the pixel bits. This  second feature was introduced in Windows 95. 

Let's examine the RLE compression first:

  • For 1-bit DIBs, the biCompression field is always BI_RGB.

  • For 4-bit DIBs, the biCompression field can be either BI_RGB or BI_RLE4.

  • For 8-bit DIBs, the biCompression field can be either BI_RGB or BI_RLE8.

  • For 24-bit DIBs, the  biCompression field is always BI_RGB.

If the value is BI_RGB, the pixel bits are stored as described for OS/2-compatible  DIBs. Otherwise, the pixel bits are compressed using run-length encoding. 

Run-length encoding (RLE) is one of the simplest forms of data compression. It  is based on the knowledge that DIB images often have strings of identical pixels in a  row. RLE saves space by encoding the value of the repeating pixel and the number of times  it is repeated. The RLE scheme used for DIBs goes somewhat beyond this in allowing a  sparse definition of the rectangular DIB image. That is, some areas of the rectangle are left  undefined. This could be used for rendering nonrectangular images.

Run-length encoding is conceptually simpler with 8-bit DIBs, so let's begin with  those. The following chart will help you in understanding how the pixel bits are encoded  when thebiCompression field equals BI_RGB8.

 

Byte 1Byte 2Byte 3Byte 4Meaning
0000  End of row
0001  End of image
0002dxdyMove to(x+dx
00n = 03 through FF  Use next n pixels
n = 01 through FFpixel  Repeat pixel n times

 

When decoding a compressed DIB, look at the DIB data bytes in pairs, as  indicated by the "Byte 1" and "Byte 2" headings in this table. The table is arranged in increasing  values of these bytes, but it makes more sense to discuss the table from the bottom up.

If the first byte is nonzero (the case shown in the last row of the table), then  that's a run-length repetition factor. The following pixel value is repeated that many times.  For example, the byte pair

0x05 0x27

decodes to the pixel values:

0x27 0x27 0x27 0x27 0x27

The DIB will, of course, have much data that does not repeat from pixel to pixel.  That's the case handled by the second-to-last row of the table. It indicates a number of pixels  that follow that should be used literally. For example, consider the sequence

0x00 0x06 0x45 0x32 0x77 0x34 0x59 0x90

It decodes to the pixel values

0x45 0x32 0x77 0x34 0x59 0x90

These sequences are always aligned on 2-byte boundaries. If the second byte is  odd, then there's an extra byte in the sequence that is unused. For example, the sequence

0x00 0x05 0x45 0x32 0x77 0x34 0x59 0x00

decodes to the pixel values

0x45 0x32 0x77 0x34 0x59

That's how the run-length encoding works. As is obvious, if there are no  repeating pixels in the DIB image, then using this compression technique will actually increase  the size of the DIB file.

The first three rows of the table shown above indicate how some  parts of the rectangular DIB image can be left undefined. Imagine yourself, or a program  you wrote, decompressing a compressed DIB. During this decompression routine, you  will maintain a number pair (y,x) starting at (0,0). You will increment  x by 1 every time you decode a pixel, resetting x to 0 and incrementingy every time you finish a row. 

When you encounter the byte 0x00 followed by 0x02, you read the next two  bytes and add them as unsigned increments to your current x andy values and then continue decoding. When you read the byte 0x00 followed by 0x00, you're done with the row.  Setx equal to 0 and incrementy. When you encounter the byte 0x00 followed by 0x01,  you're done decoding. These codes allow the DIB to contain areas that are not defined, which  is sometimes useful for encoding a nonrectangular image or for making digital  animations or movies (because each frame mostly has information from the previous frame and  need not be recoded).

For 4-bit DIBs, the encoding is generally the same but is complicated  somewhat because there isn't a one-to-one correspondence between bytes and pixels.

If the first byte you read is nonzero, that's a repetition factor  n. The second byte (which is to be repeated) contains 2 pixels, which alternate in the decoded sequence for n pixels. For example, the pair

0x07 0x35

is decoded as

0x35 0x35 0x35 0x3?

where the question mark indicates that the pixel is as yet unknown. If the pair 0x07  0x35 shown above is followed by the pair

0x05 0x24

then the full decoded sequence becomes

0x35 0x35 0x35 0x32 0x42 0x42

If the first byte in the pair is 0x00 and the second is 0x03 or greater, use the  number of pixels indicated by the second byte. For example, the sequence

0x00 0x05 0x23 0x57 0x10 0x00

decodes to

0x23 0x57 0x1?

Notice that the encoded sequence must be padded to have an even number of bytes.

Whenever the biCompression field is BI_RLE4 or BI_RLE8, the  biSizeImage field indicates the size of the DIB pixel data in bytes. If the biCompression field is BI_RGB, then biSizeImage is usually 0, but it could be set to biHeight times the byte length of the row, as calculated earlier in this chapter.

The current documentation says that "Top-down DIBs cannot be compressed."  The top-down DIBs are those with negative biHeight fields.

Color Masking

The biCompression field is also used in conjunction with the 16-bit and 32-bit DIBs  that were new with Windows 95. For these DIBs, the biCompression field can be either BI_RGB or BI_BITFIELDS (defined as equaling the value 3).

As a review, let's look at the pixel format of the 24-bit DIB, which always has  abiCompression field equal to BI_RGB: 

click here to view full size

That is, each row is basically an array of RGBTRIPLE structures, with possible padding  at the end of the row so that the number of bytes in the row is a multiple of 4.

For a 16-bit DIB with a biCompression field of BI_RGB, each pixel requires two  bytes. The colors are encoded like so:

click here to view full size

Each color uses five bits. For the first pixel in the row, the blue value is the  least-significant five bits of the first byte. The green value requires bits from the first and second byte:  the two most-significant bits of the green value are the two least-significant bits of the  second byte, and the three least-significant bits of the green value are the three  most-significant bits of the first byte. The red value is bits 2 through 6 of the second byte. The  most-significant bit of the second byte is 0.

This makes a whole lot more sense when you access the pixel value as a 16-bit  word. Because the least-significant bytes of multibyte values are stored first, the pixel word  looks like this:

click here to view full size

Suppose you have the 16-bit pixel stored in  wPixel. You can calculate the red, green, and blue values like so:

Red   = ((0x7C00 & wPixel) >> 10) << 3 ;
Green = ((0x03E0 & wPixel) >>  5) << 3 ;
Blue  = ((0x001F & wPixel) >>  0) << 3 ;

First, the pixel undergoes a bitwise AND operation with a mask value. The result  is shifted right 10 bits for red, 5 bits for green, and 0 bits for blue. I will be referring to  these shift values as "right-shift" values. This produces color values in the range 0x00  through 0x1F. The values must then be shifted left 3 bits so that the resultant color values  range from 0x00 through 0xF8. I will refer to these shift values as "left-shift" values.

And keep this in mind: if the pixel width of a 16-bit DIB is odd, each row will  have an extra 2 bytes padded at the end to achieve a byte width divisible by 4.

For a 32-bit DIB, if biCompression equals BI_RGB, each pixel requires 4 bytes.  The blue color value is the first byte, green is the second, red is the third, and the fourth  byte equals 0. In other words, the pixels are an array of RGBQUAD structures. Because  each pixel is 4 bytes in length, padding is never required at the end of the row.

If you access each pixel as a 32-bit double word, it looks like this:

click here to view full size

Or, if dwPixel is the 32-bit double word,

Red   = ((0x00FF0000 & dwPixel) >> 16) << 0 ;
Green = ((0x0000FF00 & dwPixel) >>  8) << 0 ;
Blue  = ((0x000000FF & dwPixel) >>  0) << 0 ;

The left-shift values are all zero because the color values are already maximized at  0xFF. Be aware that this double word isnot consistent with the 32-bit COLORREF value used  to specify RGB color in Windows GDI function calls. In the COLORREF value, red is the  least-significant byte.

So far, we've covered the default case for 16-bit and 32-bit DIBs when the  biCompression field is BI_RGB. If the  biCompression field is BI_BITFIELDS, the  BITMAPINFOHEADER structure of the DIB is immediately followed by three 32-bit color masks, the  first for red, the second for green, and the third for blue. You use the C bitwise AND  operator (&) to apply these masks to the 16-bit or 32-bit pixel value. You then shift the result  right by right-shift values, which are unfortunately unknown until you examine the masks  themselves. The rules regarding these color masks should be obvious when you think  about them: the 1 bits in each color mask must be contiguous, and the 1 bits must not  overlap among the three masks.

Let's take an example. You have a 16-bit DIB, and the  biCompression field is BI_BITFIELDS. You examine the first three double words following the  BITMAPINFOHEADER structure:

0x0000F800
0x000007E0
0x0000001F 

Note that only bits among the bottom 16 bits are set to 1 because this is a 16-bit  DIB. You set the variablesdwMask[0]dwMask[1], and dwMask[2] to these values. Now you  write little routines that calculate right-shift and left-shift values from the masks:

int MaskToRShift (DWORD dwMask)
{
     int iShift ;

     if (dwMask == 0)
          return 0 ;

     for (iShift = 0 ; !(dwMask & 1)  ; iShift++)
          dwMask >>= 1 ;

     return iShift ;
}

int MaskToLShift (DWORD dwMask)
{
     int iShift ;

     if (dwMask == 0)
          return 0 ;

     while (!(dwMask & 1))
          dwMask >>= 1 ;

     for (iShift = 0 ; dwMask & 1 ; iShift++)
          dwMask >>= 1 ;

     return 8 - iShift ;
}

Then you call the MaskToRShift function three times to obtain right-shift values:

iRShift[0] = MaskToRShift (dwMask[0]) ;
iRShift[1] = MaskToRShift (dwMask[1]) ;
iRShift[2] = MaskToRShift (dwMask[2]) ;

You get values 11, 5, and 0, respectively. You can then call  MaskToLShift similarly:

iLShift[0] = MaskToLShift (dwMask[0]) ;
iLShift[1] = MaskToLShift (dwMask[1]) ;
iLShift[2] = MaskToLShift (dwMask[2]) ;

You get values of 3, 2, and 3, respectively. Now you can extract each color from  the pixel value:

Red   = ((dwMask[0] & wPixel) >> iRShift[0]) << iLShift[0] ;
Green = ((dwMask[1] & wPixel) >> iRShift[1]) << iLShift[1] ;
Blue  = ((dwMask[2] & wPixel) >> iRShift[2]) << iLShift[2] ;

The procedure is the same for 32-bit DIBs except that the color masks can be greater  than 0x0000FFFF, which is the maximum mask value for 16-bit DIBs. 

Note that with either 16-bit or 32-bit DIBs, the red, green, and blue color values  can be greater than 255. In fact, in a 32-bit DIB, if two of the masks are 0, the third could  be 0xFFFFFFFF, for a 32-bit color value! Of course, this is somewhat ridiculous, so I  wouldn't worry about it too much.

Unlike Windows NT, Windows 95 and Windows 98 have some restrictions when  using color masks. The only allowable values are shown in the table below.

 

 16-Bit DIB16-Bit DIB32-Bit DIB
Red Mask0x00007C000x0000F8000x00FF0000
Green Mask0x000003E00x000007E00x0000FF00
Blue Mask0x0000001F0x0000001F0x000000FF
Shorthand5-5-55-6-58-8-8

 

In other words, you can use the two sets of masks that you would get by default  whenbiCompression is BI_RGB, plus the set of masks I showed above in the example. The  bottom row in the table shows a shorthand notation for indicating the number of red, green,  and blue bits per pixel.

The Version 4 Header

We're not quite finished yet. As I mentioned, Windows 95 changed some of the  original BITMAPINFOHEADER field definitions. Windows 95 also included a new expanded  information header called BITMAPV4HEADER. The name of this structure is clear when  you realize that Windows 95 might have been called Windows 4.0 and that this structure  was also supported by Windows NT 4.0.

typedef struct 
{
DWORD        bV4Size ;           // size of the structure = 120
LONG         bV4Width ;          // width of the image in pixels
LONG         bV4Height ;         // height of the image in pixels
WORD         bV4Planes ;         // = 1
WORD         bV4BitCount ;       // bits per pixel (1, 4, 8, 16, 24, or 32)
DWORD        bV4Compression ;    // compression code
DWORD        bV4SizeImage ;      // number of bytes in image
LONG         bV4XPelsPerMeter ;  // horizontal resolution
LONG         bV4YPelsPerMeter ;  // vertical resolution
DWORD        bV4ClrUsed ;        // number of colors used
DWORD        bV4ClrImportant ;   // number of important colors
DWORD        bV4RedMask ;        // Red color mask
DWORD        bV4GreenMask ;      // Green color mask
DWORD        bV4BlueMask ;       // Blue color mask
DWORD        bV4AlphaMask ;      // Alpha mask
DWORD        bV4CSType ;         // color space type
CIEXYZTRIPLE bV4Endpoints ;      // XYZ values
DWORD        bV4GammaRed ;       // Red gamma value
DWORD        bV4GammaGreen ;     // Green gamma value
DWORD        bV4GammaBlue ;      // Blue gamma value
}
BITMAPV4HEADER, * PBITMAPV4HEADER ;

Notice that the first 11 fields are the same as in the BITMAPINFOHEADER  structure. The last five fields support the image color-matching technology of Windows 95 and  Windows NT 4.0. Unless you use the last four fields of the BITMAPV4HEADER structure,  you should use BITMAPINFOHEADER (or BITMAPV5HEADER) instead.

The bV4RedMask, bV4GreenMask, and  bV4BlueMask values are applicable only for 16-bit and 32-bit DIBs when the bV4Compression field equals BI_BITFIELDS. These  serve the same function as the color masks defined in the BITMAPINFOHEADER structure  and actually occur in the same place in the DIB file as when using the original structure  except that here they are explicit structure fields. As far as I know, the bV4AlphaMask field is not used.

The remaining fields in the BITMAPV5HEADER structure involve Windows  Image Color Management, which I'm afraid is a subject beyond the scope of this book.  However, a little background may help get you started.

The problem with using an RGB scheme for color is that it is dependent on the  technologies of video monitors, color cameras, and color scanners. If a color is specified  as the RGB value (255, 0, 0), all that means is that a maximum voltage should be applied  to the red electron gun in a cathode ray tube. An RGB value of (128, 0, 0) indicates that  half the voltage is to be applied. Monitors can differ in their response. Moreover, printers  use a different method of color that involves combinations of cyan, magenta, yellow, and  black inks. These methods are known as CMY (cyan-magenta-yellow) and CMYK  (cyan-magenta-yellow-black). Mathematical formulas can translate RGB values to CMY and CMYK, but  there is no guarantee that the printer color will match a monitor color. Image Color  Management is an attempt to relate colors to device-independent standards.

The phenomenon of color is related to the wavelengths of visible light, which  range from 380 nanometers (blue) to 780 nm (red). Any light that we visually perceive is  made up of combinations of different amounts of various wavelengths in the visible  spectrum. In 1931, the Commission Internationale de L'Éclairage (International Commission on  Illumination) or CIE developed a method for scientifically quantifying color. This involves  using three color-matching functions (named, and ) that in their abridged form (with  values for every 5 nm) are documented in CIE Publication 15.2-1986, "Colorimetry,  Second Edition," Table 2.1. 

A spectrum (S) of a color is a set of values that indicate the strength of each  wavelength. If a spectrum is known, the color-matching functions can be applied to the  spectrum to calculateX,Y, and  Z:     

These values are called Big X, Big  Y, and Big Z. The color-matching function is  equivalent to the response of the human eye to the range of light in the visible spectrum. (It  looks like a bell curve that goes to 0 at 380 nm and 780 nm.)Y is called the CIE Luminance because it indicates an overall intensity of the light.

If you're using the BITMAPV5HEADER structure, the  bV4CSType field must be set to LCS_CALIBRATED_RGB, which is equal to 0. The next four fields must be set to valid values.

The CIEXYZTRIPLE structure is defined like so:

typedef struct tagCIEXYZTRIPLE
{
     CIEXYZ  ciexyzRed ;
     CIEXYZ  ciexyzGreen ;
     CIEXYZ  ciexyzBlue ;
}
CIEXYZTRIPLE, * LPCIEXYZTRIPLE ;

And the CIEXYZ structure is

typedef struct tagCIEXYZ
{
     FXPT2DOT30 ciexyzX ;
     FXPT2DOT30 ciexyzY ;
     FXPT2DOT30 ciexyzZ ;
}
CIEXYZ, * LPCIEXYZ ;

The three fields are defined as FXPT2DOT30 values, which means that they are  interpreted as fixed-point values with a 2-bit integer part and a 30-bit fractional part. Thus,  0x40000000 is 1.0 and 0x48000000 is 1.5. The maximum value 0xFFFFFFFF is just a smidgen under 4.0.

The bV4Endpoints field provides three  X, Y, and Z values that correspond to  the RGB colors (255, 0, 0), (0, 255, 0), and (0, 0, 255). These values should be inserted  by the application that creates the DIB to indicate the device-independent meaning of  these RGB colors.

The remaining three fields of BITMAPV4HEADER refer to "gamma." Gamma  (the lowercase Greek letter g) refers to a nonlinearity in the specification of color levels. In  a DIB, levels of red, green, and blue range from 0 through 255. On the video board,  these three digital values are converted to three analog voltages that go to the monitor.  The voltages determine the intensity of each pixel. However, due to characteristics of the  electronics of the electron guns in a cathode ray tube, the intensity  (I) of the pixel is not linearly related to the voltage  (V). Instead, the relationship is

I = (V + e)g

where e is the black-level of the monitor set by the monitor's Brightness control.  (Preferably this is 0.) The exponent g is set by the monitor's Picture or Contrast control. For  most monitors, g is about 2.5.

To compensate for this nonlinearity, video cameras have traditionally included  "gamma correction" in their circuitry. The light input to a camera is modified by an exponent  of 0.45. This implies a video display gamma of about 2.2. (The higher gamma of video  displays increases the contrast somewhat, which is usually not undesirable because  ambient light tends to lower contrast.)

This nonlinear response of video monitors is actually much more felicitous than  it may at first seem. This is because human response to light is also nonlinear. Earlier  I mentioned thatY is called CIE Luminance. This is a linear measure of light. The CIE  also defines a Lightness value that approximates human perception. Lightness is L* (pronounced "ell star") and is calculated from Y using the formulas

where Yn is a white level. The first part of the formula is a small linear segment.  Generally, human perception of lightness is related to the cube root of the linear luminance,  which is indicated by the second formula.  L* ranges from 0 to 100. Each integral increment of L* is generally assumed to be the smallest change in lightness that humans can perceive.

It is preferable to code light intensities based on perceptual lightness rather than  linear luminance. This keeps the number of bits down to a reasonable level and also reduces  noise in analog circuity.

Let's go through the whole process. The pixel value  (P) ranges from 0 to 255. This is linearly converted to a voltage level, which we can assume is normalized to a  value between 0.0 and 1.0. Assuming the monitor's black level is set to 0, the intensity of  the pixel is

where g is probably about 2.5. Human perception of lightness  (L*) is based on the cube root of this intensity and ranges from 0 to 100, so approximately

That exponent will be about 0.85 or so. If the exponent were 1, then CIE lightness  would be perfectly matched to pixel values. We don't have quite that situation, but it's much  closer than if the pixel values indicated linear luminance.

The last three fields of the BITMAPV4HEADER provide a way for programs that  create a DIB to indicate a gamma value assumed for the pixel values. These values are  interpreted as 16-bit integer values and 16-bit fractional values. For example, 0x10000 is 1.0.  If the DIB is created by capturing a real-world image, this gamma value is probably  implied by the capture hardware and will probably be 2.2 (encoded as 0x23333). If the DIB  is generated algorithmically by a program, the program would convert any linear  luminances it uses to CIE lightness values using a power function. The inverse of the exponent  would be the gamma encoded in the DIB.

The Version 5 Header

Programs written for Windows 98 and Windows NT 5.0 can use DIBs that have a  new BITMAPV5HEADER information structure:

typedef struct 
{
DWORD        bV5Size ;           // size of the structure = 120
LONG         bV5Width ;          // width of the image in pixels
LONG         bV5Height ;         // height of the image in pixels
WORD         bV5Planes ;         // = 1
WORD         bV5BitCount ;       // bits per pixel (1, 4, 8, 16, 24, or 32)
DWORD        bV5Compression ;    // compression code
DWORD        bV5SizeImage ;      // number of bytes in image
LONG         bV5XPelsPerMeter ;  // horizontal resolution
LONG         bV5YPelsPerMeter ;  // vertical resolution
DWORD        bV5ClrUsed ;        // number of colors used
DWORD        bV5ClrImportant ;   // number of important colors
DWORD        bV5RedMask ;        // Red color mask
DWORD        bV5GreenMask ;      // Green color mask
DWORD        bV5BlueMask ;       // Blue color mask
DWORD        bV5AlphaMask ;      // Alpha mask
DWORD        bV5CSType ;         // color space type
CIEXYZTRIPLE bV5Endpoints ;      // XYZ values
DWORD        bV5GammaRed ;       // Red gamma value
DWORD        bV5GammaGreen ;     // Green gamma value
DWORD        bV5GammaBlue ;      // Blue gamma value
DWORD        bV5Intent ;         // rendering intent
DWORD        bV5ProfileData ;    // profile data or filename
DWORD        bV5ProfileSize ;    // size of embedded data or filename
DWORD        bV5Reserved ;
}
BITMAPV5HEADER, * PBITMAPV5HEADER ;

This has four new fields, only three of which are used. These fields support a  proposal made by the International Color Consortium (founded by Adobe, Agfa, Apple,  Kodak, Microsoft, Silicon Graphics, Sun Microsystems, and others) called the ICC Profile Format Specification. You can obtain a copy of this from  http://www.icc.org. Basically, each input (scanner  or camera), output (printer or film recorder), and display (monitor) device is associated  with a profile that relates the native device-dependent colors (generally RGB or CMYK) to  a device-independent color specification, ultimately based on CIE XYZ values. These  profiles have filenames with the extension .ICM (for "image color management"). A profile  can be embedded into a DIB file or linked from the DIB file to indicate how the DIB  was created. You can obtain more information about Image Color Management in Windows  at/Platform SDK/Graphics and Multimedia Services/Color Management.

The bV5CSType field in the BITMAPV5HEADER can take on several different  values. If it's LCS_CALIBRATED_RGB, then it's compatible with the BITMAPV4HEADER  structure. ThebV5Endpoints field and the gamma fields must be valid.

If the bV5CSType field is LCS_sRGB, none of the remaining fields need to be set.  The implied color space is a "standard" RGB color space devised by Microsoft and  Hewlett-Packard to attempt some relative device independence, particularly across the  Internet, without the bulk of profiles. This is documented at  http://www.color.org/contrib/sRGB.html.

If the bV5CSType field is LCS_WINDOWS_COLOR_SPACE, none of the  remaining fields need be set. Windows uses the color space implied by API function calls for  displaying the bitmap.

If the bV5CSType field is PROFILE_EMBEDDED, the DIB file contains an ICC  profile. If the field is PROFILE_LINKED, the DIB file contains the fully qualified filename of an  ICC profile. In either case,bV5ProfileData is an offset from the beginning of the  BITMAPV5HEADER to the start of the profile data or filename. The bV5ProfileSize field gives the size of the data or filename. The endpoints and gamma fields need not be set.

Displaying DIB Information

It is now time to look at some code. We don't know enough to actually display a DIB  just yet, but we can at least display information about the DIB from the header structures.  The DIBHEADS program shown in Figure 15-1 does this.

Figure 15-1. The DIBHEADS program.

 

DIBHEADS.C

/*-----------------------------------------------
   DIBHEADS.C -- Displays DIB Header Information
                 (c) Charles Petzold, 1998
  -----------------------------------------------*/



#include <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

TCHAR szAppName[] = TEXT ("DibHeads") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     HACCEL   hAccel ;
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = szAppName ;
     wndclass.lpszClassName = szAppName ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"), 
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("DIB Headers"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT, 
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

     hAccel = LoadAccelerators (hInstance, szAppName) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          if (!TranslateAccelerator (hwnd, hAccel, &msg))
          {
               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
          }
     }
     return msg.wParam ;
}

void Printf (HWND hwnd, TCHAR * szFormat, ...)
{
     TCHAR   szBuffer [1024] ;
     va_list pArgList ;

     va_start (pArgList, szFormat) ;
     wvsprintf (szBuffer, szFormat, pArgList) ;
     va_end (pArgList) ;

     SendMessage (hwnd, EM_SETSEL, (WPARAM) -1, (LPARAM) -1) ;
     SendMessage (hwnd, EM_REPLACESEL, FALSE, (LPARAM) szBuffer) ;
     SendMessage (hwnd, EM_SCROLLCARET, 0, 0) ;
}
void DisplayDibHeaders (HWND hwnd, TCHAR * szFileName)
{
     static TCHAR     * szInfoName [] = { TEXT ("BITMAPCOREHEADER"), 
                                          TEXT ("BITMAPINFOHEADER"),
                                          TEXT ("BITMAPV4HEADER"),
                                          TEXT ("BITMAPV5HEADER") } ;
     static TCHAR     * szCompression [] = { TEXT ("BI_RGB"), TEXT ("BI_RLE8"),
                                             TEXT ("BI_RLE4"),
                                             TEXT ("BI_BITFIELDS"),
                                             TEXT ("unknown") } ;
     BITMAPCOREHEADER * pbmch ;
     BITMAPFILEHEADER * pbmfh ;
     BITMAPV5HEADER   * pbmih ;
     BOOL               bSuccess ;
     DWORD              dwFileSize, dwHighSize, dwBytesRead ;
     HANDLE             hFile ;
     int                i ;
     PBYTE              pFile ;
     TCHAR            * szV ;

          // Display the file name

     Printf (hwnd, TEXT ("File: %s\r\n\r\n"), szFileName) ;

          // Open the file

     hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                         OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ;

     if (hFile == INVALID_HANDLE_VALUE)
     {
          Printf (hwnd, TEXT ("Cannot open file.\r\n\r\n")) ;
          return ;
     }
          
          // Get the size of the file

     dwFileSize = GetFileSize (hFile, &dwHighSize) ;

     if (dwHighSize)
     {
          Printf (hwnd, TEXT ("Cannot deal with >4G files.\r\n\r\n")) ;
          CloseHandle (hFile) ;
          return ;
     }
          // Allocate memory for the file

     pFile = malloc (dwFileSize) ;

     if (!pFile)
     {
          Printf (hwnd, TEXT ("Cannot allocate memory.\r\n\r\n")) ;
          CloseHandle (hFile) ;
          return ;
     }

          // Read the file

     SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
     ShowCursor (TRUE) ;

     bSuccess = ReadFile (hFile, pFile, dwFileSize, &dwBytesRead, NULL) ;

     ShowCursor (FALSE) ;
     SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

     if (!bSuccess || (dwBytesRead != dwFileSize))
     {
          Printf (hwnd, TEXT ("Could not read file.\r\n\r\n")) ;
          CloseHandle (hFile) ;
          free (pFile) ;
          return ;
     }

          // Close the file

     CloseHandle (hFile) ;

          // Display file size

     Printf (hwnd, TEXT ("File size = %u bytes\r\n\r\n"), dwFileSize) ;

          // Display BITMAPFILEHEADER structure

     pbmfh = (BITMAPFILEHEADER *) pFile ;

     Printf (hwnd, TEXT ("BITMAPFILEHEADER\r\n")) ;
     Printf (hwnd, TEXT ("\t.bfType = 0x%X\r\n"), pbmfh->bfType) ;
     Printf (hwnd, TEXT ("\t.bfSize = %u\r\n"), pbmfh->bfSize) ;
     Printf (hwnd, TEXT ("\t.bfReserved1 = %u\r\n"), pbmfh->bfReserved1) ;
     Printf (hwnd, TEXT ("\t.bfReserved2 = %u\r\n"), pbmfh->bfReserved2) ;
     Printf (hwnd, TEXT ("\t.bfOffBits = %u\r\n\r\n"), pbmfh->bfOffBits) ;

          // Determine which information structure we have

     pbmih = (BITMAPV5HEADER *) (pFile + sizeof (BITMAPFILEHEADER)) ;

     switch (pbmih->bV5Size)
     {
     case sizeof (BITMAPCOREHEADER):  i = 0 ;                       break ;
     case sizeof (BITMAPINFOHEADER):  i = 1 ;  szV = TEXT ("i")  ;  break ;
     case sizeof (BITMAPV4HEADER):    i = 2 ;  szV = TEXT ("V4") ;  break ;
     case sizeof (BITMAPV5HEADER):    i = 3 ;  szV = TEXT ("V5") ;  break ;
     default:
          Printf (hwnd, TEXT ("Unknown header size of %u.\r\n\r\n"), 
                        pbmih->bV5Size) ;
          free (pFile) ;
          return ;
     }

     Printf (hwnd, TEXT ("%s\r\n"), szInfoName[i]) ;

          // Display the BITMAPCOREHEADER fields

     if (pbmih->bV5Size == sizeof (BITMAPCOREHEADER))
     {
          pbmch = (BITMAPCOREHEADER *) pbmih ;

          Printf (hwnd, TEXT ("\t.bcSize = %u\r\n"), pbmch->bcSize) ;
          Printf (hwnd, TEXT ("\t.bcWidth = %u\r\n"), pbmch->bcWidth) ;
          Printf (hwnd, TEXT ("\t.bcHeight = %u\r\n"), pbmch->bcHeight) ;
          Printf (hwnd, TEXT ("\t.bcPlanes = %u\r\n"), pbmch->bcPlanes) ;
          Printf (hwnd, TEXT ("\t.bcBitCount = %u\r\n\r\n"), pbmch->bcBitCount) ;
          free (pFile) ;
          return ;
     }

          // Display the BITMAPINFOHEADER fields

     Printf (hwnd, TEXT ("\t.b%sSize = %u\r\n"), szV, pbmih->bV5Size) ;
     Printf (hwnd, TEXT ("\t.b%sWidth = %i\r\n"), szV, pbmih->bV5Width) ;
     Printf (hwnd, TEXT ("\t.b%sHeight = %i\r\n"), szV, pbmih->bV5Height) ;
     Printf (hwnd, TEXT ("\t.b%sPlanes = %u\r\n"), szV, pbmih->bV5Planes) ;
     Printf (hwnd, TEXT ("\t.b%sBitCount = %u\r\n"), szV, pbmih->bV5BitCount) ;
     Printf (hwnd, TEXT ("\t.b%sCompression = %s\r\n"), szV, 
                   szCompression [min (4, pbmih->bV5Compression)]) ;
     Printf (hwnd, TEXT ("\t.b%sSizeImage = %u\r\n"), szV, pbmih->bV5SizeImage) ;
     Printf (hwnd, TEXT ("\t.b%sXPelsPerMeter = %i\r\n"), szV, 
                   pbmih->bV5XPelsPerMeter) ;
     Printf (hwnd, TEXT ("\t.b%sYPelsPerMeter = %i\r\n"), szV, 
                   pbmih->bV5YPelsPerMeter) ;
     Printf (hwnd, TEXT ("\t.b%sClrUsed = %i\r\n"), szV, pbmih->bV5ClrUsed) ;
     Printf (hwnd, TEXT ("\t.b%sClrImportant = %i\r\n\r\n"), szV, 
                   pbmih->bV5ClrImportant) ;

     if (pbmih->bV5Size == sizeof (BITMAPINFOHEADER))
     {
          if (pbmih->bV5Compression == BI_BITFIELDS)
          {
               Printf (hwnd, TEXT ("Red Mask   = %08X\r\n"), 
                             pbmih->bV5RedMask) ;
               Printf (hwnd, TEXT ("Green Mask = %08X\r\n"), 
                             pbmih->bV5GreenMask) ;
               Printf (hwnd, TEXT ("Blue Mask  = %08X\r\n\r\n"), 
                             pbmih->bV5BlueMask) ;
          }
          free (pFile) ;
          return ;
     }

          // Display additional BITMAPV4HEADER fields

     Printf (hwnd, TEXT ("\t.b%sRedMask   = %08X\r\n"), szV, 
                   pbmih->bV5RedMask) ;
     Printf (hwnd, TEXT ("\t.b%sGreenMask = %08X\r\n"), szV, 
                   pbmih->bV5GreenMask) ;
     Printf (hwnd, TEXT ("\t.b%sBlueMask  = %08X\r\n"), szV, 
                   pbmih->bV5BlueMask) ;
     Printf (hwnd, TEXT ("\t.b%sAlphaMask = %08X\r\n"), szV, 
                   pbmih->bV5AlphaMask) ;
     Printf (hwnd, TEXT ("\t.b%sCSType = %u\r\n"), szV, 
                   pbmih->bV5CSType) ;
     Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzRed.ciexyzX   = %08X\r\n"),  
                   szV, pbmih->bV5Endpoints.ciexyzRed.ciexyzX) ;
     Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzRed.ciexyzY   = %08X\r\n"), 
                   szV, pbmih->bV5Endpoints.ciexyzRed.ciexyzY) ;
     Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzRed.ciexyzZ   = %08X\r\n"), 
                   szV, pbmih->bV5Endpoints.ciexyzRed.ciexyzZ) ;
     Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzGreen.ciexyzX = %08X\r\n"), 
                   szV, pbmih->bV5Endpoints.ciexyzGreen.ciexyzX) ;
     Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzGreen.ciexyzY = %08X\r\n"), 
                   szV, pbmih->bV5Endpoints.ciexyzGreen.ciexyzY) ;
     Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzGreen.ciexyzZ = %08X\r\n"), 
                   szV, pbmih->bV5Endpoints.ciexyzGreen.ciexyzZ) ;
     Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzBlue.ciexyzX  = %08X\r\n"),
                   szV, pbmih->bV5Endpoints.ciexyzBlue.ciexyzX) ;
     Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzBlue.ciexyzY  = %08X\r\n"), 
                   szV, pbmih->bV5Endpoints.ciexyzBlue.ciexyzY) ;
     Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzBlue.ciexyzZ  = %08X\r\n"), 
                   szV, pbmih->bV5Endpoints.ciexyzBlue.ciexyzZ) ;
     Printf (hwnd, TEXT ("\t.b%sGammaRed   = %08X\r\n"), szV, 
                   pbmih->bV5GammaRed) ;
     Printf (hwnd, TEXT ("\t.b%sGammaGreen = %08X\r\n"), szV, 
                   pbmih->bV5GammaGreen) ;
     Printf (hwnd, TEXT ("\t.b%sGammaBlue  = %08X\r\n\r\n"), szV, 
                   pbmih->bV5GammaBlue) ;

     if (pbmih->bV5Size == sizeof (BITMAPV4HEADER))
     {
          free (pFile) ;
          return ;
     }

          // Display additional BITMAPV5HEADER fields

     Printf (hwnd, TEXT ("\t.b%sIntent = %u\r\n"), szV, pbmih->bV5Intent) ;
     Printf (hwnd, TEXT ("\t.b%sProfileData = %u\r\n"), szV, 
                   pbmih->bV5ProfileData) ;
     Printf (hwnd, TEXT ("\t.b%sProfileSize = %u\r\n"), szV, 
                   pbmih->bV5ProfileSize) ;
     Printf (hwnd, TEXT ("\t.b%sReserved = %u\r\n\r\n"), szV, 
                   pbmih->bV5Reserved) ;

     free (pFile) ;
     return ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HWND         hwndEdit ;
     static OPENFILENAME ofn ;
     static TCHAR        szFileName [MAX_PATH], szTitleName [MAX_PATH] ;
     static TCHAR        szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")
                                      TEXT ("All Files (*.*)\0*.*\0\0") ;
     
     switch (message)
     {
     case WM_CREATE:
          hwndEdit = CreateWindow (TEXT ("edit"), NULL,
                         WS_CHILD | WS_VISIBLE | WS_BORDER | 
                              WS_VSCROLL | WS_HSCROLL |
                              ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY, 
                         0, 0, 0, 0, hwnd, (HMENU) 1,
                         ((LPCREATESTRUCT) lParam)->hInstance, NULL) ;

          ofn.lStructSize       = sizeof (OPENFILENAME) ;
          ofn.hwndOwner         = hwnd ;
          ofn.hInstance         = NULL ;
          ofn.lpstrFilter       = szFilter ;
          ofn.lpstrCustomFilter = NULL ;
          ofn.nMaxCustFilter    = 0 ;
          ofn.nFilterIndex      = 0 ;
          ofn.lpstrFile         = szFileName ;
          ofn.nMaxFile          = MAX_PATH ;
          ofn.lpstrFileTitle    = szTitleName ;
          ofn.nMaxFileTitle     = MAX_PATH ;
          ofn.lpstrInitialDir   = NULL ;
          ofn.lpstrTitle        = NULL ;
          ofn.Flags             = 0 ;
          ofn.nFileOffset       = 0 ;
          ofn.nFileExtension    = 0 ;
          ofn.lpstrDefExt       = TEXT ("bmp") ;
          ofn.lCustData         = 0 ;
          ofn.lpfnHook          = NULL ;
          ofn.lpTemplateName    = NULL ;
          return 0 ;

     case WM_SIZE:
          MoveWindow (hwndEdit, 0, 0, LOWORD (lParam), HIWORD (lParam), TRUE) ;
          return 0 ;

     case WM_COMMAND:
          switch (LOWORD (wParam))
          {
          case IDM_FILE_OPEN:
               if (GetOpenFileName (&ofn))
                    DisplayDibHeaders (hwndEdit, szFileName) ;

               return 0 ;
          }

          break ;
     
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
	 

 

DIBHEADS.RC (excerpts)

//Microsoft Developer Studio generated resource script.


#include "resource.h"
#include "afxres.h"



// Accelerator

DIBHEADS ACCELERATORS DISCARDABLE 
BEGIN
    "O",            IDM_FILE_OPEN,          VIRTKEY, CONTROL, NOINVERT
END


// Menu

DIBHEADS MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Open\tCtrl+O",        IDM_FILE_OPEN
    END
END

 

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.
// Used by DibHeads.rc



#define IDM_FILE_OPEN                   40001

 

This program has a short WndProc function that creates a read-only edit  window filling its client area and that processes File Open commands from the menu. It uses  the standard File Open dialog box invoked from the GetOpenFileName function and then calls the large function  DisplayDibHeaders. This function reads the entire DIB file into  memory and displays all the header information field by field.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值