Using OS/2 2.x bitmap filesWritten by Timur Tabi |
IntroductionThis 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. Rumor 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 halftoning algorithms), please let me know. I'll include it in my second edition! The OS/2 2.x Bitmap File FormatOS/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 color 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 color table, and the actualy pel data. Bitmap information structureStructure BITMAPINFOHEADER2 contains information that defines the size and type of the bitmap, but it says nothing about the colors 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;
The Color TableThe color 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 color value, which is the maximum that OS/2 can handle. There is also a 32-bit color 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 colors with the GpiAnimatePalette() API. The PM Reference online documentation provides this cryptic exaplanation for PC_EXPLICIT: "The low-order word of the color 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 color 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 color table is nothing more than an array of RGB2 structures. The length of the array defines the number of colors in the bitmap. Ideally, the values of the color 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 DataThe 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 boundry. In other words, a 256-color 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 an N-bit value in the pel data. A 256-color bitmap uses one byte for each pixel, and a 24-bit bitmap uses three bytes per pixel. The color table usually contains entries only for those colors used in the bitmap, although it may contain the entire palette. A 24-bit bitmap does not require a color table, although the Icon Editor tends to include one anyway. Single and Multiple bitmap filesThe 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;
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 color 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. 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;
Creating a Bitmap from a .BMP fileCreating 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. #include <iostream.h> #include <io.h> #include <fcntl.h> #include <malloc.h> int load(const char *szName, char* &bitmap) { int fh=_open(szName,O_RDONLY | O_BINARY); if (fh == -1) { cout << "Error opening file " << szName << ".\n"; return 0; } int length=_filelength(fh); if (length == -1) { cout << "Error determining length for " << szName << ".\n"; _close(fh); return 0; } if (!length) { cout << szName << " has zero filesize.\n"; _close(fh); return 0; } bitmap=(char *) malloc(length); if (!bitmap) { cout << "Error allocating " << length << " bytes.\n"; _close(fh); return 0; } int bytesread=_read(fh,bitmap,length); if (!bytesread) { cout << "Read past end of file " << szName << ".\n"; free(bitmap); _close(fh); return 0; } if (bytesread == -1) { cout << "Error reading file " << szName << ".\n"; free(bitmap); _close(fh); return 0; } if (bytesread != length) { cout << "Could only read " << bytesread << " of " << length << " bytes.\n"; free(bitmap); _close(fh); return 0; } if (_close(fh)) { cout << "Error closing " << szName << ".\n"; free(bitmap); return 0; } return length; } 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. Parsing a Bitmap FileOnce 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 color 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 BitmapOnce 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: #define INCL_WINWINDOWMGR #define INCL_GPIBITMAPS #define INCL_DEV #include <os2.h> #include <malloc.h> #include <memory.h> 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 color 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 color table. The bitmap file format always places the color table immediately after the corresponding BITMAPINFOHEADER2 structure, so you would think that there is no need to pass a BITMAPINFOHEADER2 and a color 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:
Determining the size of the color tableIf the bitmap is not a 24-bit bitmap, then there's a chance that the color 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 color table. If this value is zero, then there is no color table. Technically speaking, a 24-bit bitmap doesn't need a color table at all. Allocating the memorySince a BITMAPINFO2 structure is equivalent to a a BITMAPINFOHEADER2 plus a color 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 color table is equal to the number of entries times sizeof(RGB2). Initializing the BITMAPINFO2 blockA pair of memcpy()'s, first for the BITMAPINFOHEADER2 and then for the color table right after it, is all it takes. Creating the presentation spaceIn 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 bitmapWe now have everything we need. Just pass all the parameters to GpiCreateBitmap(). CleanupThe 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 BitmapsIf 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 colors, one that's 32x32 with 16 colors, 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 colors 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 color 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 colors, and you have two bitmaps defined: 800x600x16 colors and 1024x768x256 colors. Which is more important, the resolution or the number of colors? If I ever write a second edition for this article, I will address this issue in more detail. The BMPINFO programIncluded 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 Joe View prior to 1.22 would not set the cbSize field correctly, so BMPINFO could not determine the the version of the file format. |