[comp.os.os2.programmer] Seminar

larrys@watson.ibm.com (05/15/91)

I'm having trouble debugging, so I am having a colleague look at my code
for bugs that I've missed.  In the meantime...

...Here's the next in the series...

Cheers,
Larry Salomon, Jr. (aka 'Q')            LARRYS@YKTVMV.BITNET
OS/2 Applications and Tools             larrys@ibmman.watson.ibm.com
IBM T.J. Watson Research Center         larrys@eng.clemson.edu
Yorktown Heights, NY

Disclaimer:  The statements and/or opinions stated above are strictly my
own and do not reflect the views of my employer.  Additionally, I have a
reputation for being obnoxious, so don't take any personal attacks too
seriously.

--------- Cut here ---------
                         OS/2 1.2 Format Bitmaps

When OS/2 1.1 came out, PM had defined an image file format standard
known (as many other formats are known) as a bitmap.  More specifically,
a bitmap file contains information about the bitmap, such as the size,
number of bits per pel, etc.  that PM needs to know to display it
properly.

Unfortunately, when bitmaps were CREATED on higher resolution displays,
they looked rather lousy on an EGA monitor.  An 8514 bitmap looks bad
even on a VGA.  So, the PM team went back into the huddle and refined
their original design, which resulted in the 1.2 bitmap file format,
which is used by 1.2 and 1.3.

The 1.2 bitmap file format is simply an array of 1.1 bitmaps with some
more information in them describing the device type that the 1.1 bitmap
is "designed" for.  So, you can have different versions of the same
bitmap to account for differences in device resolution and number of
colors.

How does this work?  When an application requests that a bitmap is to be
loaded, PM searches through the array (assuming the file is a 1.2
format bitmap) attempting to match the display device size (in pels) with
two fields in the bitmap array header (cxDisplay and cyDisplay).  If a
match is found, the a bitmap is created using the corresponding bitmap
definition in the file.  If a match is not found, the "independent" form
is used (the "independent" form is defined to have cxDisplay and
cyDisplay equal to 0).  If the "independent" form is non-existant as
well, the load fails.

So, using this as a basis, let's get done to the "nitty-gritty".

The 1.2 bitmap file format is as follows:

---------------------------+--------------------------------------
BITMAPARRAYFILEHEADER      | Looking in PMBITMAP.H, we find the
 | |                       | definitions for BITMAPARRAYFILEHEADER
 | +- BITMAPFILEHEADER     | and BITMAPFILEHEADER.
 |     |                   |
 |     +- BITMAPINFOHEADER | typedef struct _BITMAPFILEHEADER {
 |     |                   |     USHORT    usType;
 |     +- RGB    the       |     ULONG     cbSize;
 |     +- RGB    color     |     SHORT     xHotspot;
 |      :        table     |     SHORT     yHotspot;
 |     +- RGB              |     ULONG     offBits;
 |                         |     BITMAPINFOHEADER bmp;
 V                         | } BITMAPFILEHEADER;
BITMAPARRAYFILEHEADER      |
 | |                       | typedef struct _BITMAPARRAYFILEHEADER {
 | +- BITMAPFILEHEADER     |     USHORT    usType;
 |     |                   |     ULONG     cbSize;
 |     +- BITMAPINFOHEADER |     ULONG     offNext;
 |     |                   |     USHORT    cxDisplay;
 |     +- RGB              |     USHORT    cyDisplay;
 |     +- RGB              |     BITMAPFILEHEADER bfh;
 |      :                  | } BITMAPARRAYFILEHEADER;
 |     +- RGB              |
 |                         | #define BFT_ICON           0x4349
 :                         | #define BFT_BMAP           0x4d42
 |                         | #define BFT_POINTER        0x5450
 V                         | #define BFT_COLORICON      0x4943
BITMAPARRAYFILEHEADER      | #define BFT_COLORPOINTER   0x5043
   |                       | #define BFT_BITMAPARRAY    0x4142
   +- BITMAPFILEHEADER     |
       |                   | The BITMAPINFOHEADER and RGB structs
       +- BITMAPINFOHEADER | can be found in PMGPI.H.
       |                   |
       +- RGB              | typedef struct _BITMAPINFOHEADER {
       +- RGB              |     ULONG  cbFix;
        :                  |     USHORT cx;
       +- RGB              |     USHORT cy;
                           |     USHORT cPlanes;
                           |     USHORT cBitCount;
---------------------------+ } BITMAPINFOHEADER;

typedef struct _RGB {
    BYTE bBlue;
    BYTE bGreen;
    BYTE bRed;
} RGB;

Knowing these structures, we can write a *rudimentary*, saveBmp()
function.

BOOL saveBmp(PCHAR pchFile,HBITMAP hbmImage)
//-----------------------------------------------
// This function saves the specified bitmap to disk.  Disclaimer:
// I wrote this code on the fly, and although it is based heavily
// on existing, well-tested code, this function has not been tested at
// all, and I will assume no liability for its performance (or lack
// thereof) - Larry Salomon, Jr.
//
// Input:  pchFile - points to the filename
//         hbmImage - bitmap handle to save
// Returns:  TRUE if successful, FALSE otherwise
//-----------------------------------------------
{
   // abBuf is used to hold the BITMAPINFOHEADER and
   // the RGB color table returned from GpiQueryBitmapBits
   BYTE abBuf[sizeof(BITMAPINFOHEADER)+sizeof(RGB)*256];
   PBITMAPINFOHEADER pbmihInfo;
   FILE *pfFile;
   HDC hdc;
   SIZEL szlHps;
   HPS hps;
   USHORT usNumClrs;
   BITMAPARRAYFILEHEADER bmafhInfo;
   LONG lTemp;
   PBYTE pbBits;

   pmbhiInfo=abBuf;

   pfFile=fopen(pchFile,"wb");
   if (pfFile==NULL) {
      return FALSE;
   } /* endif */

   // Note that GpiQueryBitmapBits operates on a PS and
   // not on a bitmap handle, as one would assume.  So,
   // create a memory HDC and HPS to set the bitmap into.
   hdc=DevOpenDC(hab,OD_MEMORY,"*",0L,NULL,NULL);
   if (hdc==NULL) {
      fclose(pfFile);
      remove(pchFile);
      return FALSE;
   } /* endif */

   GpiQueryBitmapParameters(hbm,pbmihInfo);
   szlHps.cx=pbmihInfo->cx;
   szlHps.cy=pbmihInfo->cy;

   hps=GpiCreatePS(hab,hdc,&szlHps,PU_PELS|GPIT_MICRO|GPIA_ASSOC);
   if (hps==NULL) {
      DevCloseDC(hdc);
      fclose(pfFile);
      remove(pchFile);
      return FALSE;
   } /* endif */

   GpiSetBitmap(hps,hbmImage);

   usNumClrs=1<<(pbmihInfo->cPlanes*pbmihInfo->cBitCount);

   // Initialize the bitmap header Note that we do NOT create an
   // "independent" device form.
   memcpy(&bmafhInfo.bmf.bmp,pbmihInfo,sizeof(BITMAPINFOHEADER));

   bmafhInfo.usType=BFT_BITMAPARRAY;
   bmafhInfo.cbSize=sizeof(BITMAPARRAYFILEHEADER);
   bmafhInfo.offNext=0;

   DevQueryCaps(hdc,CAPS_WIDTH,1L,&lTemp);
   bmafhInfo.cxDisplay=lTemp;

   DevQueryCaps(hdc,CAPS_HEIGHT,1L,&lTemp);
   bmafhInfo.cyDisplay=lTemp;

   bmafhInfo.bfh.usType=BFT_BMAP;
   // In Appendix B of the Technical Reference, there is information
   // pertaining to 1.1 bitmap file formats.  It does say that cbSize
   // in the BITMAPFILEHEADER structure must be the size of the bitmap
   // file in bytes.
   bmafhInfo.bfh.cbSize=(LONG)(sizeof(BITMAPFILEHEADER)+
                           sizeof(RGB)*usNumClrs+
                           ((pbmihInfo->cBitCount*pbmihInfo->cx+31L)/32L)*
                           pbmihInfo->cPlanes*4L*pbmihInfo->cy);
   bmafhInfo.bfh.xHotspot=0;
   bmafhInfo.bfh.yHotspot=0;
   bmafhInfo.bfh.offBits=sizeof(BITMAPARRAYFILEHEADER)+
                            sizeof(RGB)*usNumClrs;

   GpiQueryBitmapBits(hps,0L,0L,NULL,(PBITMAPINFO)pbmihInfo);

   // Write the bitmap header
   fwrite(&bmafhInfo,sizeof(bmafhInfo),1,pfFile);
   fwrite((PVOID)((&pbmihInfo)+sizeof(BITMAPINFOHEADER)),
          sizeof(RGB)*usNumClrs,
          1,
          pfFile);

   // Calculate the size of the buffer needed to hold the entire
   // bitmap.  Note that this assumes that the data will fit in
   // 64K.  I did say that this was a rudimentary function...  ;)
   lTemp=(LONG)((pbmihInfo->cBitCount*pbmihInfo->cx+31L)/32L)*
            pbmihInfo->cPlanes*4L*pbmihInfo->cy;
   pbBits=(PBYTE)malloc((SHORT)lTemp);
   if (pbBits==NULL) {
      GpiSetBitmap(hps,NULL);
      GpiDestroyPS(hps);
      DevCloseDC(hdc);
      fclose(pfFile);
      remove(pchFile);
      return FALSE;
   } /* endif */

   // Query the bits and write them out
   GpiQueryBitmapBits(hps,
                      0L,
                      pbmihInfo->cy,
                      pbBits,
                      (PBITMAPINFO)pbmihInfo);
   fwrite(pbBits,(SHORT)lTemp,1,pfFile);

   // Clean up and exit
   GpiSetBitmap(hps,NULL);
   GpiDestroyPS(hps);
   DevCloseDC(hdc);
   fclose(pfFile);
   return TRUE;
}