Open GL Super Bible

Previous Table of Contents Next


Panning a Pixmap

The glPixelStore function can be used to pan inside an image. For example, to display the center 300 x 300 pixel area of a 640 x 480 pixel image, you would use

glPixelStorei(GL_UNPACK_ROW_LENGTH, 640);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, (640 - 300) / 2);
glPixelStorei(GL_UNPACK_SKIP_ROWS, (480 - 300) / 2);
glDrawPixels(300, 300, GL_RGB, GL_UNSIGNED_BYTE, BitmapBits);

In this example, the GL_UNPACK_ROW_LENGTH value specifies the width of the original image in pixels. Set this when the width specified with glDrawPixels is different from the width of the image.

GL_UNPACK_SKIP_PIXELS specifies the number of pixels to skip on the left side of the image. Here we skip the first (640 – 300) / 2, or 170 pixels on the left side of the image to show the middle.

GL_UNPACK_SKIP_ROWS is similar but specifies the number of rows or scanlines in the image to skip. Normally, this value represents the number of rows from the bottom, but you can change this by specifying a negative Y scaling with glPixelZoom.


NOTE:  The GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_PIXELS, and GL_UNPACK_SKIP_ROWS attributes refer to the original pixmap size in pixels, not the size after zooming!

Reading Pixmaps

OpenGL provides a function called glReadPixels that can read an image from the screen. Beyond the obvious application of saving your created image to disk, it can also be used for cool effects with texture mapping.

Unlike glDrawPixels, glReadPixels ignores the current raster position and requires you to specify an (x,y) viewport coordinate for the lower-left corner of the image to read. Listing 11-8 demonstrates how to read the current viewport into a Windows bitmap structure suitable for saving to a file or using as a texture.

Listing 11-8 ReadDIBitmap function

/*
 * 'ReadDIBitmap()' - Read the current OpenGL viewport into a
 *                    24-bit RGB bitmap.
 *
 * Returns the bitmap pixels if successful and NULL otherwise.
 */

void *
ReadDIBitmap(BITMAPINFO **info) /* O - Bitmap information */
{
  long    i, j,                 /* Looping var */
          bitsize,              /* Total size of bitmap */
          width;                /* Aligned width of a scanline */
  GLint   viewport[4];          /* Current viewport */
  void    *bits;                /* RGB bits */
  GLubyte *rgb,                 /* RGB looping var */
          temp;                 /* Temporary var for swapping */
 /*
  * Grab the current viewport…
  */

  glGetIntegerv(GL_VIEWPORT, viewport);

 /*
  * Allocate memory for the header and bitmap…
  */

  if ((*info = (BITMAPINFO *)malloc(sizeof(BITMAPINFOHEADER))) == NULL)
  {
   /*
    * Couldn't allocate memory for bitmap info - return NULL…
    */

    return (NULL);
  };

  width   = viewport[2] * 3;     /* Real width of scanline */
  width   = (width + 3) & ~3;    /* Aligned to 4 bytes */
  bitsize = width * viewport[3]; /* Size of bitmap, aligned */

  if ((bits = calloc(bitsize, 1)) == NULL)
  {
   /*
    * Couldn't allocate memory for bitmap pixels - return NULL…
    */

    free(*info);
    return (NULL);
  };

 /*
  * Read pixels from the framebuffer…
  */

  glFinish();                          /* Finish all OpenGL commands */
  glPixelStorei(GL_PACK_ALIGNMENT, 4); /* Force 4-byte alignment */
  glPixelStorei(GL_PACK_ROW_LENGTH, 0);
  glPixelStorei(GL_PACK_SKIP_ROWS, 0);
  glPixelStorei(GL_PACK_SKIP_PIXELS, 0);

  glReadPixels(0, 0, viewport[2], viewport[3], GL_RGB, GL_UNSIGNED_BYTE,
               bits);

 /*
  * Swap red and blue for the bitmap…
  */

  for (i = 0; i < viewport[3]; i ++)
    for (j = 0, rgb = ((GLubyte *)bits) + i * width;
         j < viewport[2];
 j ++, rgb += 3)
    {
      temp   = rgb[0];
      rgb[0] = rgb[2];
      rgb[2] = temp;
    };
 /*
  * Finally, initialize the bitmap header information…
  */

  (*info)->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
  (*info)->bmiHeader.biWidth         = viewport[2];
  (*info)->bmiHeader.biHeight        = viewport[3];
  (*info)->bmiHeader.biPlanes        = 1;
  (*info)->bmiHeader.biBitCount      = 24;
  (*info)->bmiHeader.biCompression   = BI_RGB;
  (*info)->bmiHeader.biSizeImage     = bitsize;
  (*info)->bmiHeader.biXPelsPerMeter = 2952; /* 75 DPI */
  (*info)->bmiHeader.biYPelsPerMeter = 2952; /* 75 DPI */
  (*info)->bmiHeader.biClrUsed       = 0;
  (*info)->bmiHeader.biClrImportant  = 0;

  return (bits);
}

The first thing you need to do is find out the size of the current viewport, using glGetIntegerv as shown just below. (This function is described in Chapter 14). This places the current X origin, Y origin, X size, and Y size into the viewport array, as shown in Table 11-3.

/*
 * Grab the current viewport…
 */

 glGetIntegerv(GL_VIEWPORT, viewport);
Table 11-3 Viewport Array Definitions

Index Description

0 X origin of viewport (pixels)
1 Y origin of viewport (pixels)
2 X size of viewport (pixels)
3 Y size of viewport (pixels)

Once you have the size of the viewport, you then allocate memory for the pixmap. It’s important to note that Windows bitmaps (and OpenGL pixmaps by default) must have the beginning of each line at a 32-bit boundary. To accomplish this, we do the following:

width   = viewport[2] * 3;     /* Real width of scanline *
width   = (width + 3) & ~3;    /* Aligned to 4 bytes */

You must round the computed actual byte width of the viewport (in this case, 3 bytes for every pixel wide) up to the nearest 32-bit (or 4-byte) boundary. The total size of the pixmap then becomes

bitsize = width * viewport[3]; /* Size of bitmap, aligned */

After allocating memory for the pixmap, we call glReadPixels to get the contents of the current viewport and fill in the Windows BITMAPHEADER structure with all the necessary information.


Previous Table of Contents Next