Back to November Calendar

Previous Entry Next Entry→

November 29, 2005

Bring in Yer Bitmap

Microsoft has a graphic format called "device independent bitmap" or DIB.  OpenGL doesn't understand this format, so you've got to do some translating.  The following information is heavilty gleaned from Dale Rogerson's article of February 8, 1995.

OpenGL Image Format

The OpenGL function glDrawPixels, renders images. Chapter 8 the "Red Book" and the OpenGL Reference Manual (also called the "Blue Book"; see the bibliography) both provide information on glDrawPixels.

The prototype for glDrawPixels is as follows:

glDrawPixels( GLsizei width,
              GLsizei height,
              GLenum format,
              Glenum type,
              const GLvoid *pixels) ;

The first two parameters specify the width and height of the image, in pixels. The last parameter is a pointer to the pixel data itself. The other two parameters, format and type, tell glDrawPixels how to interpret the data that the pixels parameter points to.

The format parameter specifies the composition of each element in the image. It can have the following values:

  • GL_RGB
  • GL_RGBA
  • GL_COLOR_INDEX
  • GL_STENCIL_INDEX
  • GL_DEPTH_COMPONENT
  • GL_RED
  • GL_GREEN
  • GL_BLUE
  • GL_ALPHA
  • GL_LUMINANCE
  • GL_LUMINANCE_ALPHA

GL_RGB and GL_RGBA, are of greatest interest to me now because these parameters follow the formats used by Microsoft Windows.

The type parameter can have the following values:

  • GL_UNSIGNED_BYTE
  • GL_BYTE
  • GL_BITMAP
  • GL_UNSIGNED_SHORT
  • GL_SHORT
  • GL_UNSIGNED_INT
  • GL_INT
  • GL_FLOAT

Windows uses GL_UNSIGNED_BYTE for its DIBs.

Consider an example:

glRasterPos3d(x, y, z) ;
glDrawPixels(cx, cy, GL_RGB, GL_UNSIGNED_BYTE, pPixels) ;

OpenGL draws the image stored at pPixels at the coordinate (x, y, z). The void* pPixels variable points to RGB triples stored as three unsigned bytes. Figure 1 summarizes what we learned about the image format by studying glDrawPixels.

Figure 1. OpenGL image format

The following example is slightly different from our previous example:

glRasterPos3d(x, y, z) ;
glDrawPixels(cx, cy, GL_RGBA, GL_UNSIGNED_BYTE, pPixels) ;

Instead of storing RGB triples, the GL_RGBA format parameter specifies that each pixel is made up of four unsigned bytes—one byte each for the red, green, blue, and alpha components.

Although examining glDrawPixels does provide us with some insight into how OpenGL stores images, we still need to answer some questions, such as how the pixels are stored in memory.

glPixelStore

The glPixelStore command controls how pixels are stored in, and read from, memory.  "Pack" refers to putting images in memory, and the term "unpack" refers to reading images from memory and putting them on the screen. We want to translate a Windows DIB into a format that OpenGL can unpack and display on the screen. Therefore, our interest lies only in the unpacking functionality of glPixelStore.

glPixelStore controls six OpenGL parameters that affect unpacking images. The following table (based on the table in Chapter 8 of the Red Book) lists these parameters:

Parameter Name Type Initial Value Valid Range
GL_UNPACK_ SWAP_BYTES GLboolean FALSE TRUE/FALSE
GL_UNPACK_LSB_FIRST GLboolean FALSE TRUE/FALSE
GL_UNPACK_ROW_LENGTH GLint 0 i > 0
GL_UNPACK_SKIP_ROWS GLint 0 i > 0
GL_UNPACK_SKIP_PIXELS GLint 0 I > 0
GL_UNPACK_ALIGNMENT GLint 4 1, 2, 4, 8

The GL_UNPACK_SWAP_BYTES parameter is needed only when transferring images between systems that order bytes differently. We can ignore this parameter because we are using the DIBs on a Microsoft Windows system.

GL_UNPACK_LSB_FIRST is used only with 1-bit-per-pixel (bpp) images, which we'll hope we don't need.

The next three parameters, GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_ROWS, and GL_UNPACK_SKIP_PIXELS, are used to display a piece of a larger image, which we'l also hope not to need.

This leaves us with one measly flag, GL_UNPACK_ALIGNMENT, which controls the alignment of pixel rows. The following table shows the allowable values for GL_UNPACK_ALIGNMENT and their meanings:

Value Meaning
1 Byte alignment (that is, no alignment)
2 Row alignment to even-numbered bytes
4 Word alignment
8 Double-word alignment

GL_UNPACK_ALIGNMENT will pad the end of a row with enough bytes to start the next row on the proper boundary.

It's amazing that the Red Book does not mention the ordering of the RGB triple in memory. As mentioned in the Blue Book, red is always stored first, followed by green, and then blue. This order cannot be modified with glPixelStore. Figure 4 summarizes what we learned about the image format by studying glPixelStore.

Figure 4. Storage of an OpenGL image

From the discussion above, we can see that an OpenGL image is a chunk of memory with a certain format, type, alignment, width, and height. Pretty simple, really.

For more information, see the section "Storing, Transforming, and Mapping Pixels" in Chapter 8 of the Red Book.

Windows DIB Format

Like OpenGL, Windows DIB in that it also consists of a chunk of memory in a specific format--but Window DIB formats vary quite a bit: 1-, 4-, 8-, 16-, 24-, and 32-bpp formats with a variety of compression types.  Worse: Win32® has no functions for loading DIBs and very few functions to help you use DIBs, (See the bibliography at the end for a listing of sources on this.  Dale Rogerson, whose article I'm relying on heavily here, recommends Nigel Thompson's book Animation Techniques for Win32 (available from Microsoft Press®) and Nigel's CDIB class to read Windows DIBs.

Storage of RGB components in DIB is reversed to BGR and the rows of the DIB are DWORD-aligned in a Windows DIB. Figure 2 shows the format of a Windows DIB.

Figure 2. 24-bpp DIB format

Loading a DIB

The CDIB class from Nigel's animation library CDIB class has a very convenient Load member function that takes a filename and loads the file. Here is the code from GLlib that loads a Windows DIB and starts the translation process:

void CGLImage::Load(LPCTSTR filename, TRANSLATEMETHOD enumMethod)
{
   //
   // Is an image already loaded?
   //

   if (m_pBits)   {
      // Clean up previous image.
      free(m_pBits );
      m_pBits = NULL ;
      m_iHeight = 0 ;
      m_iWidth = 0 ;
   }

   //
   // Create a DIB using Nigel's animation library.
   //

   CDIB aDib ;
   aDib.Load(filename) ;

   // Get width and height in pixels.
   m_iWidth    = aDib.GetWidth() ;
   m_iHeight    = aDib.GetHeight() ;

   //
   // Create a palette from the colors in the DIB.
   //

   CDIBPal aPalSrc ;
   aPalSrc.Create(&aDib) ;

   //
   // Translate using one of three methods.
   //

   m_enumMethod = enumMethod;
   switch(m_enumMethod)
   {
   case TRANS_GDI:
      m_PixelFormat = GL_RGBA ;
      TranslateByGDI(aDib, aPalSrc) ;
      break;
   case TRANS_BOTH:
      m_PixelFormat = GL_RGB ;
      TranslateByBoth(aDib, aPalSrc) ;
      break;
   case TRANS_DIY:
   default:
      m_PixelFormat = GL_RGB ;
      TranslateByYourself(aDib, aPalSrc) ;
   }
}

Nigel's CDIBPal class conveniently creates a palette from a CDIB object's color table, which can really simplify this part of the code.

Translation

Here are three methods for translating the Windows DIB to a format usable by OpenGL. In the first, we write all the translation code ourselves. In the second method, we use the graphics device interface (GDI) to do the work. In the last method, we use GDI to do part of the translation and provide some extra code to finish the work. The translation methods are called from CGLImage::Load in GLlib, as listed above.

Do-It-Yourself Method

Here we write all the code that translates a DIB to an OpenGL image. Because we write the code, we know exactly what the translation process is. The disadvantage of this method is that a DIB can have many different formats, all of which the translation code must handle. The source code in CGLImage::TranslateByYourself handles 4-, 8-, and 24-bpp DIBs. The rest are left as an exercise for the student.

CGLImage::TranslateByYourself converts Windows DIBs to an array of 24-bpp structures (CGLRGBTRIPLE) with no padding. A skeleton of TranslateByYourself is shown below. The details of the actual translation are given later in this section.

int nBitsPerPix = aDib.GetBitsPerPixel() ;
void* pBits     = aDib.GetBitsAddress() ;
int storageWidth = aDib.GetStorageWidth() ; // storage width in BYTES!

CGLRGBTRIPLE* pGLRGB =
            (CGLRGBTRIPLE*)malloc(m_iWidth*m_iHeight*sizeof(CGLRGBTRIPLE));
CGLRGBTRIPLE* pDest = pGLRGB ;

switch(nBitsPerPixel)
{
   case 24:
      .
      .
      .
      break ;
   case 8:
      .
      .
      .
      break ;
      .
      .
      .
}
The image is rendered with the OpenGL command:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1) ;
glDrawPixels(m_iWidth, m_iHeight, GL_RGB, GL_UNSIGNED_BYTE, m_pBits) ;

glPixelStore turns padding off. The call to glDrawPixels uses the GL_RGB pixel format to specify a 24-bpp format for the memory that m_pBits points to.

As I mentioned above, CGLImage::TranslateByYourself creates a new chunk of memory consisting of CGLRGBTRIPLE structures, which are defined in IMAGE.CPP as follows:

typedef struct _CGLRGBTRIPLE {
   BYTE rgbRed ;
   BYTE rgbGreen ;
   BYTE rgbBlue ;
} CGLRGBTRIPLE ;

The placement of the red and blue color components in this structure differs from the placement of components in the Windows RGBTRIPLE structure. Windows places the blue color component first in memory, whereas OpenGL looks for the red color component first.

typedef struct _RGBTRIPLE { //rgbt
   BYTE rgbtBlue ;
   BYTE rgbtGreen ;
   BYTE rgbtRed ;
} RGBTRIPLE ;

Figure 3. Ordering of color components in memory

24-bpp DIBs

The conversion of 24-bpp Windows DIBs to OpenGL images consists of

  • removing the boundary padding and
  • rearranging the bits from an RGBTRIPLE format to the CGLRGBTRIPLE format:
.
.
.
int storageWidth = aDib.GetStorageWidth() ; // storage width in BYTES!
CGLRGBTRIPLE* pDest = pGLRGB ;
.
.
.

   case 24:
      {
         RGBTRIPLE* pSrc = (RGBTRIPLE*) pBits ;
         int widthDiff = storageWidth - m_iWidth*sizeof(RGBTRIPLE) ;
         for(int j = 0 ; j < m_iHeight ; j++)
         {
            for(int i = 0 ; i < m_iWidth ; i++)
            {
               pDest->rgbRed = pSrc->rgbtRed ;
               pDest->rgbGreen = pSrc->rgbtGreen ;
               pDest->rgbBlue = pSrc->rgbtBlue ;
               pDest++ ;
               pSrc++ ;
            }
            pSrc = (RGBTRIPLE*)( (BYTE*)pSrc + widthDiff) ;
         }
      }
      break ;

 

The storageWidth variable contains the actual width (in bytes) of the DIB in memory, including padding. storageWidthis used to remove the padding at the end of the DIB.

8-bpp DIBs

Unlike the 24-bpp DIB format, the 8-bpp DIB format uses a color table. Nigel's CDIB class contains the GetClrTabAddress function, which returns a pointer to the DIB's color table. Translating the 8-bpp DIB into a form usable by OpenGL consists of looking up the color of each pixel and inserting this color into the destination image. The color is converted from an RGBQUAD into a CGLRGBTRIPLE, and padding is removed in the process.

case 8:
      {
         BYTE* pSrc = (BYTE*) pBits ;
         RGBQUAD* pClrTab = aDib.GetClrTabAddress() ;
         int widthDiff = storageWidth - m_iWidth ;
         for(int j = 0 ; j < m_iHeight ; j++)
         {
            for(int i = 0 ; i < m_iWidth ; i++)
            {
               pDest->rgbRed = pClrTab[*pSrc].rgbRed ;
               pDest->rgbGreen = pClrTab[*pSrc].rgbGreen ;
               pDest->rgbBlue = pClrTab[*pSrc].rgbBlue ;
               pDest++ ;
               pSrc++ ;
            }
            pSrc += widthDiff ;
         }
      }
      break ;

4-bpp DIBs and other formats

The code for translating 4-bpp DIBs is included in CGLImage::TranslateByYourself. You have enough information to write the remaining formats yourself.

GDI Method

The GDI method uses the Win32 StretchDIBits function to perform the translation. This method creates a DIB section in a format that OpenGL can use, and the original DIB is drawn on the new DIB section with StretchDIBits. We then pass the pointer to the bits to OpenGL, so it can render the picture.

The challenge of this method is trying to find the overlap between the set of formats that StretchDIBits supports and the set that OpenGL supports. The difficulty lies in the order of the color components in memory. Again, Windows puts blue first while OpenGL puts red first.

32-bpp DIBs come to the rescue! Based on the theory that more is better, the Windows DIB specification was extended to include 16- and 32-bpp formats. Although these additions do not directly improve life (try finding any 16- or 32-bpp DIBs), the extension to the DIB specification does help us rearrange the color components.

BI_BITFIELDS

We can customize the byte ordering with 16- or 32-bpp DIBs. To customize, set BITMAPINFOHEADER.biCompression to BI_BITFIELDS, and then supply three DWORDs (or RGBQUADs) containing masks that determine the pixel layout. The first DWORD contains the red mask, the second DWORD contains the green mask, and the third DWORD contains the blue mask. Using RGBQUADs instead of DWORDs can make the reversal of bytes more obvious in our case:

BITMAPINFO* pBMI ;
.
.
.
// Red mask moves the red byte into blue's normal position.
pBMI->bmiColors[0].rgbBlue = 0xff ;

// Green mask doesn't move location of green byte.
pBMI->bmiColors[1].rgbGreen = 0xff ;

// Blue mask moves the blue byte into red's normal position.
pBMI->bmiColors[2].rgbRed = 0xff ;

See "Formats Using BI_BITFIELDS and Color Masks" in the "Bitmap Format Extensions" section of the Video for Windows SDK (in Product Documentation, SDKs, Win32 SDK for Windows 3.5) for more information on BI_BITFIELDS.

To use the byte reordering feature of BI_BITFIELDS, we must use 32-bpp DIBs. To get OpenGL to display a 32-bpp DIB instead of a 24-bpp DIB, we must use GL_RGBA instead of GL_RGB as a format specifier:

glPixelStorei(GL_UNPACK_ALIGNMENT, 1) ;
glDrawPixels(m_iWidth, m_iHeight, GL_RGBA, GL_UNSIGNED_BYTE, m_pBits) ;

This approach wastes one byte per pixel. It also assumes that the driver will place a zero in the fourth location, which becomes the alpha component in OpenGL.

Another problem with this method is that it works only in Windows NT™. In Windows 95, CreateDIBSection will create an arbitrary format with BI_BITFIELDS, but the other GDI calls (for example, BitBlt) will only support a subset of the possible BI_BITFIELD masks. For 32-bpp DIBs, the GDI calls will only support 8-8-8 BGR with BI_BITFIELDS.

CGLImage::TranslateByGDI

The code for translating a Windows DIB into an OpenGL image using GDI is shown below. The code creates a new 32-bpp DIB section and uses the BI_BITFIELDS style to customize the color component arrangement in memory. It then draws the Windows DIB in the new DIB section using the CDIB::Draw from Nigel's animation library. The resulting image is in a format that OpenGL can use.

void CGLImage::TranslateByGDI(CDIB& aDib, CDIBPal& aPalSrc)
{
   //
   // Allocate memory for new DIB section header. Include
   // room for 3 color masks. BITMAPINFO includes one RGBQUAD.
   //

    int iSize = sizeof(BITMAPINFO) + 2 * sizeof(RGBQUAD);
    BITMAPINFO* pBMI = (BITMAPINFO*) malloc(iSize);
    memset(pBMI, 0, iSize);

    // Fill in the header info.
    pBMI->bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    pBMI->bmiHeader.biWidth       = m_iWidth;
    pBMI->bmiHeader.biHeight      = m_iHeight;
    pBMI->bmiHeader.biPlanes      = 1;
    pBMI->bmiHeader.biBitCount    = 32 ;           // Must use 32 bpps
    pBMI->bmiHeader.biCompression = BI_BITFIELDS ; // to use this flag.

   //
   // Rearrange RGB component storage from BGR to RGB.
   //

   pBMI->bmiColors[0].rgbBlue  = 0xff ; // Store red in blue's position.
   pBMI->bmiColors[1].rgbGreen = 0xff ; // Green stays the same.
   pBMI->bmiColors[2].rgbRed   = 0xff ; // Store blue in red's position.

   //
   // Create the new 32-bpp DIB section.
   //

   CDC dc;
   dc.CreateCompatibleDC(NULL);
   BYTE* pBits ;
   HBITMAP hbmBuffer = CreateDIBSection( dc.GetSafeHdc(),
                                         pBMI,
                                         DIB_RGB_COLORS,
                               (VOID **) &pBits,
                                         NULL,
                                         0);

   // Select DIB into DC.
   HBITMAP hbmOld = (HBITMAP)::SelectObject(dc.GetSafeHdc(), hbmBuffer);

   // Blt the Windows DIB into our new DIB.
   dc.SelectPalette(&aPalSrc,0) ;
   dc.RealizePalette() ;
   aDib.Draw(&dc,0,0) ; // Use Nigel's Animation class.
   GdiFlush() ;

   //
   // Copy the bits out of the DIB section. This allows us to delete
   // the DIB section since we don't need it. It's possible to keep
   // the DIB section around and use pBits directly.
   //

   int iImageSize = m_iWidth * m_iHeight * sizeof(RGBQUAD) ;
   m_pBits = malloc(iImageSize) ;
   memcpy(m_pBits, pBits, iImageSize) ;

   //
   // Get rid of all the GDI stuff.
   //

    if (hbmOld)
        (HBITMAP)::SelectObject(dc.GetSafeHdc(), hbmOld);

   DeleteObject(hbmBuffer) ;
}

In the code above, the bits out of the DIB section are copied into a new chunk of memory, and then deleted.  This step makes TranslateByGDI more like TranslateByYourself, resulting in a much simpler class for demonstration purposes. However, this step is not necessary; it wastes time, especially when dealing with large images.

You can change CGLImage so it only uses TranslateByGDI, and then pass the pBits pointer returned by the CreateDIBSection call to glDrawPixels. Then you don't have to waste time copying the image again.

Partial GDI Method

The last method here is the favorite of Dale Rogerson.  It combines the do-it-yourself method and the GDI method in one nice, clean method. Use the GDI to translate the bitmap from its original format to a 32-bpp format. Using 32 bpp instead of 24 bpp means that I don't have to worry about boundary padding. Then write code to rearrange the bytes from 32-bpp BGR ordering to 24-bit RGB ordering.

This method has three major advantages: It doesn't rely on BI_BITFIELDS, it doesn't waste any bytes of memory, and it will run in Windows 95. We use the same command for rendering the image that we used in the do-it-yourself method:

glPixelStorei(GL_UNPACK_ALIGNMENT, 1) ;
glDrawPixels(m_iWidth, m_iHeight, GL_RGB, GL_UNSIGNED_BYTE, m_pBits) ;

The code for CGLImage::TranslateByBoth is given below:

void CGLImage::TranslateByBoth(CDIB& aDib, CDIBPal& aPalSrc)
{
   //
   // Allocate memory for new DIB section. 
   // No color table is needed.
   //
   const int BITMAPINFOHEADER_SIZE = sizeof(BITMAPINFOHEADER) ;
   BYTE* abBitmapInfo[BITMAPINFOHEADER_SIZE] ;
   BITMAPINFOHEADER* pBMIH = (BITMAPINFOHEADER*)abBitmapInfo;
   memset(pBMIH, 0, BITMAPINFOHEADER_SIZE);

   // Fill in the header info.
   pBMIH->biSize         = sizeof(BITMAPINFOHEADER);
   pBMIH->biWidth        = m_iWidth;
   pBMIH->biHeight       = m_iHeight;
   pBMIH->biPlanes       = 1;
   pBMIH->biBitCount     = 32 ; 
   pBMIH->biCompression  = BI_RGB ; 

   //
   // Create the new 32-bpp DIB section.
   //
   CDC dc;
   dc.CreateCompatibleDC(NULL);
   BYTE* pBits ;
   HBITMAP hbmBuffer = CreateDIBSection( dc.GetSafeHdc(),
                           (BITMAPINFO*) pBMIH,
                                         DIB_RGB_COLORS,
                               (VOID **) &pBits,
                                         NULL,
                                         0);
   // Select DIB into DC.
   HBITMAP hbmOld = (HBITMAP)::SelectObject(dc.GetSafeHdc(), hbmBuffer);

   // Blt the Windows DIB into our new DIB.
   dc.SelectPalette(&aPalSrc,0) ;
   dc.RealizePalette() ;
   aDib.Draw(&dc,0,0) ;
   GdiFlush() ;

   //
   // Copy the bits out of the DIB section. 
   // Change from BGR to RGB.
   //
   int iImageSize = m_iWidth * m_iHeight * sizeof(CGLRGBTRIPLE) ;
   m_pBits = malloc(iImageSize) ;

   RGBQUAD* pSrc = (RGBQUAD*)pBits ;
   CGLRGBTRIPLE* pDest = (CGLRGBTRIPLE*) m_pBits ;
   for (int i = 0 ; i < m_iWidth*m_iHeight ; i++)
   {
      pDest->rgbRed = pSrc->rgbRed ;
      pDest->rgbGreen = pSrc->rgbGreen ;
      pDest->rgbBlue = pSrc->rgbBlue ;
      pDest++ ;
      pSrc++ ;
   }

   //
   // Get rid of all the GDI stuff.
   //
   if (hbmOld)
        (HBITMAP)::SelectObject(dc.GetSafeHdc(), hbmOld);

   DeleteObject(hbmBuffer) ;
}

There are billions and billions of other combinations you can choose from, but now you get the picture.

CGLImage

Dale Rogerson has encapsulated the code for translating Windows DIBs to OpenGL images in the CGLImage class, which is in the GLlib dynamic-link library (DLL). After CGLImage loads the DIB with Nigel's CDIB class, it translates the DIB.

The following example shows how to translate a DIB using the do-it-yourself method:

anImage.Load("MD500D.BMP", TRANS_DIY) ;

The following example shows how to translate a DIB using the GDI method:

anImage.Load("MD500D.BMP", TRANS_GDI) ;

The last two examples load an image and convert it using the partial GDI method:

CGLImage anImage ;
anImage.Load("MD500D.BMP") ; 

anImage.Load("MD500D.BMP", TRANS_BOTH) ;

The only other member function currently included in CGLImage is DrawPixels, which conveniently calls glDrawPixels:

void CGLImage::DrawPixels(CGL* pGL)
{
   ASSERT((m_iWidth != 0) && (m_iHeight !=0)) ;
   ASSERT(m_pBits) ;
   pGL->MakeCurrent() ;
   GLint iAlign ;
   glGetIntegerv(GL_UNPACK_ALIGNMENT, &iAlign) ;
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   glDrawPixels(m_iWidth, m_iHeight, m_PixelFormat,
                    GL_UNSIGNED_BYTE, m_pBits) ;

   glPixelStorei(GL_UNPACK_ALIGNMENT, iAlign);
}

Dale Rogerson's EasyDIB sample application calls CGLImage::Load in CScene::OnInit and in CSceneVw::OnOptionsLoadBitmap. CGLImage::DrawPixels is called in CScene::OnRender.

In his "OpenGL III: Building an OpenGL C++ Class" article, Dale Rogerson explained the value of not encapsulating the OpenGL functions. He justifies encapsulating glDrawPixels this way:

  • Images are translated differently on different systems. Encapsulating glDrawPixels simplifies the support of multiple systems, thus enhancing portability. Currently, CGLImage contains multiple translation functions, which can easily translate images from the X Window System or Silicon Graphics to OpenGL.
  • The glDrawPixels parameters suggest a structure. Working with structures is easier from member functions.

Changes to GLlib

Dale Rogerson modified the GLlib DLL by adding the CGLImage class. He also changed the CGL class by modifying CGL::Create and adding CGL::OnCreate.

CGL::Create and CGL::OnCreate

He changed CGL::Create to take the PIXELFORMATDESCRIPTOR.dwFlags as a parameter. This gives the user of the class more control over the pixel format. Currently, CGL does not allow you to render OpenGL commands on a bitmap. Dale Rogerson promised to add this support and document it in a future article.  Not sure that he has yet...

CGL::Create now makes a call to CGL::OnCreate after setting up the PIXELFORMATDESCRIPTOR structure. Applications that use CGL can set up the CGL instance either by calling CGL::Create or by overriding CGL::OnCreate and changing the pixel format there.

CGL::OnCreate is very similar to the CWnd::OnPreCreateWindow function in the Microsoft Foundation Class Library (MFC). The EasyDIB sample application overrides CGL::OnCreate in the CScene class. EasyDIB turns double buffering off in CScene::OnCreate, so it does not have to implement CSceneView::OnCreate and make a call to CGL::Create as EasyCI did. Another benefit of this arrangement is that it places more of the OpenGL-oriented code in CScene instead of spreading it out to CSceneView.

Conclusion

Translating Windows DIBs into a format that OpenGL can use is not difficult, once you understand the format that Windows uses and the format that OpenGL expects. You might say that it's kind of like translating English to English.

Bibliography

Sources of Information on OpenGL

Crain, Dennis. "Windows NT OpenGL: Getting Started." April 1994. (MSDN Library, Technical Articles)

Neider, Jackie, Tom Davis, and Mason Woo. OpenGL Programming Guide: The Official Guide to Learning OpenGL, Release 1. Reading, MA: Addison-Wesley, 1993. ISBN 0-201-63274-8. (This book is also known as the "Red Book".)

OpenGL Architecture Review Board. OpenGL Reference Manual: The Official Reference Document for OpenGL, Release 1. Reading, MA: Addison-Wesley, 1992. ISBN 0-201-63276-4. (This book is also known as the "Blue Book".)

Prosise, Jeff. "Advanced 3-D Graphics for Windows NT 3.5: Introducing the OpenGL Interface, Part I." Microsoft Systems Journal 9 (October 1994). (MSDN Library Archive Edition, Library, Books and Periodicals)

Prosise, Jeff. "Advanced 3-D Graphics for Windows NT 3.5: The OpenGL Interface, Part II." Microsoft Systems Journal 9 (November 1994). (MSDN Library Archive Edition, Books and Periodicals)

Prosise, Jeff. "Understanding Modelview Transformations in OpenGL for Windows NT." Microsoft Systems Journal 10 (February 1995).

Rogerson, Dale. "OpenGL I: Quick Start.". December 1994. (MSDN Library, Technical Articles)

Rogerson, Dale. "OpenGL II: Windows Palettes in RGBA Mode". December 1994. (MSDN Library, Technical Articles)

Rogerson, Dale. "OpenGL III: Building an OpenGL C++ Class." January 1995. (MSDN Library, Technical Articles)

Rogerson, Dale. "OpenGL IV: Color Index Mode." January 1995. (MSDN Library, Technical Articles)

Rogerson, Dale. "OpenGL VI: Rendering on DIBs with PFD_DRAW_TO_BITMAP." April 1995. (MSDN Library, Technical Articles)

Rogerson, Dale. "OpenGL VII: Scratching the Surface of Texture Mapping." May 1995. (MSDN Library, Technical Articles)

Microsoft Win32 Software Development Kit (SDK) for Windows NT 3.5 OpenGL Programmer's Reference.

Sources of Information on DIBs

Gery, Ron. "DIBs and Their Use.". March 1992. (MSDN Library, Technical Articles)

Gery, Ron. "Using DIBs with Palettes." March 1992. (MSDN Library, Technical Articles)

Microsoft Win32 Software Development Kit (SDK) for Windows NT 3.5 Video for Windows

Rodent, Herman. "16- and 32-Bit-Per-Pixel DIB Formats for Windows: The Color of Things to Come." January 1993. (MSDN Library Archive, Technical Articles)

Thompson, Nigel. Animation Techniques for Win32. Redmond, WA: Microsoft Press, 1995.

 

Had to get that out of my system.

 

More on Pixels - Drawing Modes

There are 16 logical operations (enabled by glEnable(GL_COLOR_LOGIC_OP)) that combine source and destination pixels bitwise in the frame buffer when logic operations are enabled by GL_COLOR_LOGIC_OP.  The default is GL_COPY (source simply replaces destination), but others include GL_COPY_INVERTED, GL_OR and GL_XOR.

The exclusive GL_XOR has some interesting properties, mostly hinging on the idea that if a and b are bits then a = xor(xor(a,b),b), that is applying the operation twice takes you back to where you started.  That is, the second draw undoes the first.

GLubyte ***bits;
    Graphics::TBitmap* bitmap;
    long BITw,BITh;
    bitmap = new Graphics::TBitmap;
    bitmap->LoadFromFile(strFileName);
    BITw = bitmap->Width;
    BITh = bitmap->Height;
    ///////
      bits = new GLubyte**[BITw];
      for (int BBj = 0; BBj < BITw; BBj++)
      {
       bits[BBj] = new GLubyte*[BITh];
       for (int BB2j = 0; BB2j < BITh; BB2j++)
        bits[BBj][BB2j] = new GLubyte[4];
      }   
    ///////
    for(int CTEi = 0; CTEi < BITw; CTEi++)
    {
    for(int CTEj = 0; CTEj < BITh; CTEj++)
        {
            bits[CTEi][CTEj][0]= (GLubyte)GetRValue(bitmap->Canvas->Pixels[CTEi][CTEj]);
            bits[CTEi][CTEj][1]= (GLubyte)GetGValue(bitmap->Canvas->Pixels[CTEi][CTEj]);
            bits[CTEi][CTEj][2]= (GLubyte)GetBValue(bitmap->Canvas->Pixels[CTEi][CTEj]);

            ///////////////////////////
            if(tEn==true)
            {
             if(bits[CTEi][CTEj][0]==tCol1 && bits[CTEi][CTEj][1]==tCol2 && bits[CTEi][CTEj][2]==tCol3)
              bits[CTEi][CTEj][3]= (GLbyte)0;
             else
              bits[CTEi][CTEj][3]= (GLbyte)255;
            }
            else
             bits[CTEi][CTEj][3]= (GLbyte)255;
            ///////////////////////////
        }
    }
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glGenTextures(1, &textureArray[textureID]);
    glBindTexture(GL_TEXTURE_2D, textureArray[textureID]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, BITw, BITh, 0, GL_RGBA, GL_UNSIGNED_BYTE, bits);
}