Using OS/2 2.x bitmap files

Written by Timur Tabi

Introduction
This article discusses the OS/2 2.x bitmap file format and how to use it in your applications. OS/2 provides support for bitmaps contained in your executable's resources and they can be loaded with a single API call. However, there may be times when combining your bitmaps with the executable is not an option; unfortunately, OS/2 has no built-in support for bitmaps stored as .BMP files. This article provides the missing elements.

It should be noted that the information article will eventually be obsolete. Rumour has it that IBM is working on a providing built-in support for loading bitmaps, including those in other graphic formats like JPEG. There are also plans for a real image editor, something that is badly needed.

The bitmap file format is also used for icons and pointers, but these two are beyond the scope of this article. The PM Reference distinguishes between bitmaps and bit maps. Bitmaps are conventional bitmap images, the kind found in .BMP files, and they are the subject of this article. A bit map is either a bitmap, an icon, or a pointer.

Nota Bene: A lot of the information on the bitmap file format is either undocumented or so rarely used that no one outside IBM (including me) actually knows what it is. If anyone has any information on the bitmap file format that is not in this article (for instance, the half-toning algorithms), please let me know. I'll include it in my second edition!

The OS/2 2.x Bitmap File Format
OS/2 1.x used a bitmap file format very similar to the Windows 3.x DIB format. OS/2 2.x provides a newer format that offers more features like compression, different units of measurement, half-toning, different colour encoding schemes, and user-defined space. Unfortunately, many of these advanced options are not documented and therefore rarely used.

OS/2 2.x supports both the old and the new bitmap formats. However, use of the old format is strongly discouraged, and this article is only concerned with the new format.

Bitmaps are divided into three parts: the bitmap information structure, the colour table, and the actually pel data.

Bitmap information structure
Structure BITMAPINFOHEADER2 contains information that defines the size and type of the bitmap, but it says nothing about the colours used or the actual image. typedef struct _BITMAPINFOHEADER2 { ULONG cbFix; ULONG cx; ULONG cy; USHORT cPlanes; USHORT cBitCount; ULONG ulCompression; ULONG cbImage; ULONG cxResolution; ULONG cyResolution; ULONG cclrUsed; ULONG cclrImportant; USHORT usUnits; USHORT usReserved; USHORT usRecording; USHORT usRendering; ULONG cSize1; ULONG cSize2; ULONG ulColorEncoding; ULONG ulIdentifier; } BITMAPINFOHEADER2;
 * cbFix:The size of the structure. Set it to sizeof(BITMAPINFOHEADER2).
 * [Editor's note - I do not normally do this, but since I know a fair amount about this topic, I will add that this is not necessarily true. The BITMAPINFOHEADER2 structure is defined semantically to be a variable-length structure meaning that you initialize up to the last field needed and set this field to be the size of the initialized portion of the structure. Thus, if you do not need any of the advanced features, you only initialize cx, cy, cPlanes, and cBitCount and set this field to 16, which is the sum of these fields' sizes.]


 * cx:The true width of the bitmap, in pixels
 * cy:The true height of the bitmap, in pixels
 * cPlanes:The number of colours planes. I've never seen this set to anything other than 1.
 * cBitCount:The number of bits/pixel, either 1, 4, 8, or 24.
 * ulCompression:The compression scheme used. These are documented in the PM Reference and can be any one of the following:
 * BCA_UNCOMP - Uncompressed
 * BCA_HUFFMAN1D - Huffman encoding, monochrome bitmaps only
 * BCA_RLE4 - Run-length encoded, 4 bits/pixel
 * BCA_RLE8 - RLE, 8/bit pixel
 * BCA_RLE24 - RLE, 24 bits/pixel


 * cbImage:The number of bytes that the pel data occupies. For an uncompressed image, this should be initialized to zero.
 * cxResolution:The horizontal resolution of the target device.
 * cyResolution:The vertical resolution of the target device.
 * cclrUsed:The number of colours in the colour table. This field is used if the colour table is smaller than the maximum number of colours. That is, if this is a 256-colour bitmap, and the colour table only defines 120 distinct colours in the bitmap, then this field is set to 120. If it's set to zero, then this is either a 24-bit bitmap of RGB values, or the colour table is full-length.
 * cclrImportant:The number of colours in the colour table that are required for satisfactory display of the bitmap. This field is used in case the system cannot map all of the colours in the table to the physical palette, so it tries to map only the first cclrImportant colours, and it approximates the rest.
 * usUnits:The units in which cxResolution and cyResolution are specified. Currently, only BRU_METRIC (pixels per meter) is supported.
 * usReserved:This is a reserved field and must be set to zero.
 * usRecording:The recording order of the pel data. The only supported value is BRA_BOTTOMUP, which means that the first row of pel data is the bottom row of the image.
 * usRendering:The half-toning algorithm for this image. None of these is documented anywhere.
 * BRH_NOTHALFTONED - This is the default.
 * BRH_ERRORDIFFUSION - (Damped) error diffusion.
 * BRH_PANDA - Processing Algorithm for Non-coded Document Acquisition.
 * BRH_SUPERCIRCLE - Super Circle.


 * cSize1:A parameter for the halftoning algorithm. It's not used if there's no half-toning.
 * cSize2:The second parameter for the PANDA and Super Circle algorithms.
 * ulColorEncoding:The type of values in the colour table. The only supported value is BCE_RGB, which means the colour table is an array of RGB2 structures.
 * ulIdentifier:Application-specific data. Use this ULONG for your own information. Great for storing pointers to additional data structures or functions, or even to C++ objects.

The Colour Table
The colour table is an array of RGB2 structures, which are defined in pmbitmap.h as: typedef struct _RGB2 { BYTE bBlue; BYTE bGreen; BYTE bRed; BYTE fcOptions; } RGB2; The first three fields are the values of blue, red, and green, respectively. Each is an eight-bit value, and together they combine to form a 24-bit colour value, which is the maximum that OS/2 can handle. There is also a 32-bit colour standard, where another eight-bit value is used to indicate the transparency, but there is no support for this standard in OS/2.

The fourth field in RGB2 is the options field. There are two flags available, PC_RESERVED and PC_EXPLICIT. PC_RESERVED is used for animating colours with the GpiAnimatePalette API. The PM Reference online documentation provides this cryptic explanation for PC_EXPLICIT:

"The low-order word of the colour table entry designates a physical palette slot. This allows an application to show the actual contents of the device palette as realized for other logical palettes. This does not prevent the colour in the slot from being changed for any reason."

I have yet to find any mention of this field in any other documentation. Fortunately, its meaning is not important for our uses - the fcOptions field is always set to zero.

The colour table is nothing more than an array of RGB2 structures. The length of the array defines the number of colours in the bitmap. Ideally, the values of the colour table will exactly match the current system palette. If not, OS/2 will automatically handle conversions. In either case, you don't have to worry about it.

The Bitmap Pel Data
The bitmap pel data is the actual bitmap data, stored as a two-dimensional array in row-major order. The dimensions of the bitmap are specified in the BITMAPINFOHEADER2 structure. Although a bitmap can be of any size, each row of data is padded to the nearest doubleword boundary. In other words, a 256-colour bitmap (eight bits/pixel) that is five pixels wide would normally take five bytes for each row. In actuality, it takes up eight bytes per row, where the last three bytes are all zeros.

Each pixel is represented by a N-bit value in the pel data. A 256-colour bitmap uses one byte for each pixel, and a 24-bit bitmap uses three bytes per pixel. The colour table usually contains entries only for those colours used in the bitmap, although it may contain the entire palette. A 24-bit bitmap does not require a colour table, although the Icon Editor tends to include one anyway.

Single and Multiple bitmap files
The OS/2 bitmap file format also supports multiple bitmaps per file. A single bitmap file is stored in this manner: BITMAPFILEHEADER2 BITMAPINFOHEADER2 Color table Pel Data BITMAPFILEHEADER2 is defined as: typedef struct _BITMAPFILEHEADER2 { USHORT usType; ULONG cbSize; SHORT xHotspot; SHORT yHotspot; ULONG offBits; BITMAPINFOHEADER2 bmp2; } BITMAPFILEHEADER2;
 * usType:The type of image, BFT_BMAP for our purposes
 * cbSize:Set to sizeof(BITMAPFILEHEADER2)
 * [Don't forget that the bmp2 field is a variable length structure. Adjust this value accordingly. - Editor]

From this one structure, two out of the three parts of the bitmap are readily available - the info structure and the pel data. The third part, the colour table, is located immediately after the info structure. The pointer manipulations are a bit tricky, but it only takes a few lines of code to get all three parts.
 * xHotspot, yHotspot:The hotspot for icons and pointers, not used for bitmaps.
 * offBits:Points to the pel data for this bitmap.
 * bmp2:The BITMAPINFOHEADER2 structure as described in the section Bitmap information structure.

When multiple bitmaps are present, each is enclosed in a BITMAPARRAYFILEHEADER2 structure. For example, this is the layout of a bitmap file with two bitmaps: BITMAPARRAYFILEHEADER2 (for bitmap #1) BITMAPFILEHEADER2 BITMAPINFOHEADER2 Color table BITMAPARRAYFILEHEADER2 (for bitmap #2) BITMAPFILEHEADER2 BITMAPINFOHEADER2 Color table Pel Data (for bitmap #1) Pel Data (for bitmap #2) The BITMAPARRAYFILEHEADER2 structure is described below. typedef struct _BITMAPARRAYFILEHEADER2 { USHORT usType; ULONG cbSize; ULONG offNext; USHORT cxDisplay; USHORT cyDisplay; BITMAPFILEHEADER2 bfh2; } BITMAPARRAYFILEHEADER2;
 * usType:Set to BFT_BITMAPARRAY, which indicates this as an array of bit maps.
 * cbSize:Set to sizeof(BITMAPARRAYFILEHEADER2)
 * offNext:Points to the next BITMAPARRAYFILEHEADER2, or 0 if this is the last one.
 * cxDisplay, cyDisplay:The resolution of the target display for this bitmap. See the section for an example of how to use these fields.
 * bfh2:The BITMAPFILEHEADER2 structure as described earlier in this section.

Creating a Bitmap from a .BMP file
Creating an OS/2 bitmap from a .BMP file requires three steps: The first part is easy. The following code opens the file, finds its length, allocates enough memory for it, and loads it into that memory. The first parameter is the name of the bitmap file to load. The second is a reference to a pointer to a block of memory. This function allocates the correct amount of memory itself and returns the pointer to that block. If all goes well, then the length of the block is returned. Otherwise, a zero indicates failure.
 * 1) Load the bitmap file into memory
 * 2) Locate the three main pieces of the bitmap file
 * 3) Use GpiCreateBitmap to create a bitmap

Parsing a Bitmap File
Once the bitmap is loaded into memory, its three subdivision must be located. The following code parses a single bitmap file: void parse(PBITMAPFILEHEADER2 pbfh, PBYTE pbBmpFile) { PBITMAPINFOHEADER2 pbih=&pbfh->bmp2; PRGB2 prgb=((PBITMAPINFO2) pbih)->argbColor; PBYTE pbPelData=pbBmpFile+pbfh->offBits; hbm=makebmp(pbih,prgb,pbPelData); } Function parse takes two parameters: a pointer to a BITMAPFILEHEADER2 structure and a pointer to the beginning of bitmap file. For a single bitmap file, these two are the same, but they are different for a multiple bitmap file.

The first line locates the bitmap info structure (BITMAPINFOHEADER2). The second line finds the colour table, and the third line finds the pel data. The last line passes these three values to makebmp, which actually creates the bitmap and is covered in the next section.

Making the Bitmap
Once the three parts of a bitmap are located, they need to be combined into a format acceptable by the GpiCreateBitmap API. The following code does just that:
 * 1) define INCL_WINWINDOWMGR
 * 2) define INCL_GPIBITMAPS
 * 3) define INCL_DEV
 * 4) include 


 * 1) include 
 * 2) include 

static int ipow(int b, int e) { int p=b; while (--e) p*=b; return p; }

HBITMAP makebmp(PBITMAPINFOHEADER2 pbih2, PRGB2 prgb2, PBYTE pbPelData) {

// Determine size of color table int iNumColors,numbits=pbih2->cPlanes * pbih2->cBitCount; if (numbits != 24) iNumColors = pbih2->cclrUsed ? pbih2->cclrUsed : ipow(2,numbits); else iNumColors = pbih2->cclrUsed; int iColorTableSize=iNumColors*sizeof(RGB2);

// Allocate storage for BITMAPINFO2 PBITMAPINFO2 pbi2=(PBITMAPINFO2) malloc(sizeof(BITMAPINFOHEADER2)+iColorTableSize); if (!pbi2) return 0;

memcpy(pbi2,pbih2,sizeof(BITMAPINFOHEADER2));                        // Copy First half memcpy((PBYTE) pbi2+sizeof(BITMAPINFOHEADER2),prgb2,iColorTableSize); // Second half

HPS hps=WinGetPS(HWND_DESKTOP); HBITMAP hbm=GpiCreateBitmap(hps,pbih2,CBM_INIT,pbPelData,pbi2); WinReleasePS(hps); free(pbi2); return hbm; } Function ipow computes b^e, which is used to compute the size of the colour table, in case the bitmap info structure doesn't specify it.

GpiCreateBitmap doesn't use a BITMAPINFOHEADER2 structure. Instead, it uses a BITMAPINFO2 structure, which is nothing more than a BITMAPINFOHEADER2 followed by a colour table. The bitmap file format always places the colour table immediately after the corresponding BITMAPINFOHEADER2 structure, so you would think that there is no need to pass a BITMAPINFOHEADER2 and a colour table. However, we want makebmp to be generic enough to take bitmap data that didn't necessarily come from a bitmap file.

Function makebmp performs the following tasks:
 * Determine the size of the colour table
 * Allocate enough space to hold the BITMAPINFOHEADER2 and the colour table
 * Copy both pieces to this block of data. This gives you a BITMAPINFO2.
 * Create a presentation space compatible with the display
 * Create the bitmap.
 * Release the presentation space and the allocated memory, since neither is needed any more.

Determining the size of the colour table
If the bitmap is not a 24-bit bitmap, then there's a chance that the colour table is 2^n entries long. If the cclrUsed field of the bitmap info structure is zero, then it is assumed that the table is full-length (i.e. 2^n entries long). Otherwise, there are cclrUsed entries.

If it is a 24-bit bitmap, then there cannot be 2^24 entries, so the cclrUsed field is guaranteed to contain the length of the colour table. If this value is zero, then there is no colour table. Technically speaking, a 24-bit bitmap doesn't need a colour table at all.

Allocating the memory
Since a BITMAPINFO2 structure is equivalent to a BITMAPINFOHEADER2 plus a colour table, we need to allocated enough enough space for both. The size of a bitmap info structure is sizeof(BITMAPINFOHEADER2) [Remember the variable-length characteristic - Editor] and the size of the colour table is equal to the number of entries times sizeof(RGB2).

Initializing the BITMAPINFO2 block
A pair of memcpy's, first for the BITMAPINFOHEADER2 and then for the colour table right after it, is all it takes.

Creating the presentation space
In order to create a bitmap, OS/2 needs to know where you're planning on using it. Since we want to display these bitmaps on the screen, we need to create an appropriate presentation space. The easiest way to do this is to call WinGetPS and pass it the handle of the desktop window.

Creating the bitmap
We now have everything we need. Just pass all the parameters to GpiCreateBitmap.

Clean-up
The bitmap that we create is self-sufficient, i.e. OS/2 makes a copy of all the data it needs, and the presentation space is only required during the creation, so we can release these two resources. That's it!

Using Multiple Bitmaps
If you've ever edited one of the standard icons that comes with OS/2, you'll notice there are several images defined. There's one that's 40x40 with 256 colours, one that's 32x32 with 16 colours, and several others. When OS/2 displays an icon, it searches the file for the best fit.

You can also use the same technique. Different bitmaps for different resolutions and colours can be created. OS/2 can scale and dither images automatically, but the results are often unsatisfactory. By defining a different bitmap for each resolution and colour depth, you can always have perfect images.

Unfortunately, this approach is error-prone. Usually you need to define a set of bitmaps that go together. And for each bitmap in the set, you'll want one for each resolution. If you create one bitmap for a certain resolution, you'll create to do the same for all other bitmaps. If you don't, then you risk having your bitmaps mismatched.

Alternatively, you could have your program automatically scale the nearest match. But what is the nearest match? Which is closer to 800x600: 640x480 or 1024x768? And what if you're running 800x600 at 256 colours, and you have two bitmaps defined: 800x600x16 colours and 1024x768x256 colours. Which is more important, the resolution or the number of colours?

If I ever write a second edition for this article, I will address this issue in more detail.

The BMPINFO program
Included with this article is the source code to BMPINFO - a program which provides a detailed dump of all bit map files, including icons and pointers. This program is useful for testing programs which need to scan or create bitmap files. It can also be used to get a better understand of the bitmap file format. It supports single and multiple bitmaps, icons, and pointers in both the new and old bitmap file formats.

Note that many programs which create bitmap files might not initialize all the fields correctly. For instance, versions of JoeView prior to 1.22 would not set the cbSize field correctly, so BMPINFO could not determine the version of the file format.