PPM / PGM / PBM image files

 

This note describes the format of PPM (Portable PixMap), PGM (Portable GreyMap), PBM (Portable BitMap) files. These formats are a convenient (simple) method of saving image data, they are equally easy to read in ones own applications. Unfortunately the standards aren't always implemented as well as they could.

These formats were popularised by the pbmplus image toolkit otherwise known as the "enhanced portable bitmap toolkit". The description of the toolkit from the man page is given below

DESCRIPTION
     The pbmplus toolkit allows conversions between  image  files
     of  different format.  By means of using common intermediate
     formats, only 2*N conversion filters are required to support
     N  distinct  formats,  instead  of  the  N^2  which would be
     required to convert directly between any one format and  any
     other.   The  package also includes simple tools for manipu-
     lating portable bitmaps.
 
     The package consists of four upwardly compatible sections:
 
     pbm  Supports monochrome bitmaps (1 bit per pixel).
 
     pgm  Supports greyscale images.  Reads  either  pbm  or  pgm
          formats and writes pgm format.
 
     ppm  Supports full-color images.  Reads either pbm, pgm,  or
          ppm formats, writes ppm format.
 
     pnm  Supports content-independent manipulations  on  any  of
          the  three  formats  listed  above, as well as external
          formats having multiple types.  Reads either pbm,  pgm,
          or  ppm  formats, and generally writes the same type as
          it read (whenever a pnm tool  makes  an  exception  and
          "promotes"  a  file  to a higher format, it informs the
          user).

PPM

A PPM file consists of two parts, a header and the image data. The header consists of at least three parts normally delinineated by carriage returns and/or linefeeds but the PPM specification only requires white space. The first "line" is a magic PPM identifier, it can be "P3" or "P6" (not including the double quotes!). The next line consists of the width and height of the image as ascii numbers. The last part of the header gives the maximum value of the colour components for the pixels, this allows the format to describe more than single byte (0..255) colour values. In addition to the above required lines, a comment can be placed anywhere with a "#" character, the comment extends to the end of the line.

The following are all valid PPM headers.

Header example 1

P6 1024 788 255

Header example 2

P6 
1024 788 
# A comment
255

Header example 3

P3
1024 # the image width
788 # the image height
# A comment
1023

The format of the image data itself depends on the magic PPM identifier. If it is "P3" then the image is given as ascii text, the numerical value of each pixel ranges from 0 to the maximum value given in the header. The lines should not be longer than 70 characters.

PPM example 4

P3
# example from the man page
4 4
15
 0  0  0    0  0  0    0  0  0   15  0 15
 0  0  0    0 15  7    0  0  0    0  0  0
 0  0  0    0  0  0    0 15  7    0  0  0
15  0 15    0  0  0    0  0  0    0  0  0

If the PPM magic identifier is "P6" then the image data is stored in byte format, one byte per colour component (r,g,b). Comments can only occur before the last field of the header and only one byte may appear after the last header field, normally a carriage return or line feed. "P6" image files are obviously smaller than "P3" and much faster to read. Note that "P6" PPM files can only be used for single byte colours.

While not required by the format specification it is a standard convention to store the image in top to bottom, left to right order. Each pixel is stored as a byte, value 0 == black, value 255 == white. The components are stored in the "usual" order, red - green - blue.

PGM

This format is identical to the above except it stores greyscale information, that is, one value per pixel instead of 3 (r,g,b). The only difference in the header section is the magic identifiers which are "P2" and "P5", these correspond to the ascii and binary form of the data respectively.

PGM example

An example of a PGM file of type "P2" is given below

P2
24 7
15
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  3  3  3  3  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15 15 15 15  0
0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0 15  0
0  3  3  3  0  0  0  7  7  7  0  0  0 11 11 11  0  0  0 15 15 15 15  0
0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0  0  0
0  3  0  0  0  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0

PBM

PBM stores single bit pixel image as a series of ascii "0" or "1"'s. Traditionally "0" refers to white while "1" refers to black. The header is identical to PPM and PGM format except there is no third header line (the maximum pixel value doesn't have any meaning. The magic identifier for PBM is "P1".

PBM example

Here is an example of a small bitmap in this format, as with PPM files there can be no more than 70 characters per line.

P1
# PBM example 
24 7
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0
0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0
0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0
0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0
0 1 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

 

 

 

------------------------------------------------------

BMP image format

 

Introduction

BMP files are an historic (but still commonly used) file format for the historic (but still commonly used) operating system called "Windows". BMP images can range from black and white (1 byte per pixel) up to 24 bit colour (16.7 million colours). While the images can be compressed, this is rarely used in practice and won't be discussed in detail here.

Structure

A BMP file consists of either 3 or 4 parts as shown in the diagram on the right. The first part is a header, this is followed by a information section, if the image is indexed colour then the palette follows, and last of all is the pixel data. The position of the image data with respect to the sart of the file is contained in the header. Information such as the image width and height, the type of compression, the number of colours is contained in the information header.

Header

The header consists of the following fields. Note that we are assuming short int of 2 bytes, int of 4 bytes, and long int of 8 bytes. Further we are assuming byte ordering as for typical (Intel) machines. The header is 14 bytes in length.

typedef struct {
   unsigned short int type;                 /* Magic identifier            */
   unsigned int size;                       /* File size in bytes          */
   unsigned short int reserved1, reserved2;
   unsigned int offset;                     /* Offset to image data, bytes */
} HEADER;

The useful fields in this structure are the type field (should be 'BM') which is a simple check that this is likely to be a legitimate BMP file, and the offset field which gives the number of bytes before the actual pixel data (this is relative to the start of the file). Note that this struct is not a multiple of 4 bytes for those machines/compilers that might assume this, these machines will generally pad this struct by 2 bytes to 16 which will unalign the future fread() calls - be warned.

Information

The image info data that follows is 40 bytes in length, it is described in the struct given below. The fields of most interest below are the image width and height, the number of bits per pixel (should be 1, 4, 8 or 24), the number of planes (assumed to be 1 here), and the compression type (assumed to be 0 here).

typedef struct {
   unsigned int size;               /* Header size in bytes      */
   int width,height;                /* Width and height of image */
   unsigned short int planes;       /* Number of colour planes   */
   unsigned short int bits;         /* Bits per pixel            */
   unsigned int compression;        /* Compression type          */
   unsigned int imagesize;          /* Image size in bytes       */
   int xresolution,yresolution;     /* Pixels per meter          */
   unsigned int ncolours;           /* Number of colours         */
   unsigned int importantcolours;   /* Important colours         */
} INFOHEADER;

The compression types supported by BMP are listed below :

Only type 0 (no compression will be discussed here.

24 bit Image Data

The simplest data to read is 24 bit true colour images. In this case the image data follows immediately after the information header, that is, there is no colour palette. It consists of three bytes per pixel in b,g,r order. Each byte gives the saturation for that colour component, 0 for black and 1 for white (fully saturated).

Indexed Colour Data

If the image is indexed colour then immediately after the information header there will be a table of infoheader.ncolours colours, each of 4 bytes. The first three bytes correspond to b,g,r components, the last byte is reserved/unused but could obviously represent the alpha channel. For 8 bit greyscale images this colour index will generally just be a greyscale ramp. If you do the sums....then the length of the header plus the length of the information block plus 4 times the number of palette colours should equal the image data offset. In other words

14 + 40 + 4 * infoheader.ncolours = header.offset

Source code

Here's source provided by Michael Sweet, BITMAP.H, BITMAP.C, and BMPVIEW.C.

And some example code, parse.c. Note that neither of these code segments will handle all types of BMP files, in particular, they don't handle compressed BMP files. They should be a good starting point to variations encountered and to those who wish to write BMP compliant files. On the other hand if you have or write a better BMP handler then you are welcome to submit for addition here.

 

/*
 * Windows BMP file definitions for OpenGL.
 *
 * Written by Michael Sweet.
 */
 
#ifndef _BITMAP_H_
#  define _BITMAP_H_
 
/*
 * Include necessary headers.
 */
 
#  include <GL/glut.h>
#  ifdef WIN32
#    include <windows.h>
#    include <wingdi.h>
#  endif /* WIN32 */
 
/*
 * Make this header file work with C and C++ source code...
 */
 
#  ifdef __cplusplus
extern "C" {
#  endif /* __cplusplus */
 
 
/*
 * Bitmap file data structures (these are defined in <wingdi.h> under
 * Windows...)
 *
 * Note that most Windows compilers will pack the following structures, so
 * when reading them under MacOS or UNIX we need to read individual fields
 * to avoid differences in alignment...
 */
 
#  ifndef WIN32
typedef struct                       /**** BMP file header structure ****/
    {
    unsigned short bfType;           /* Magic number for file */
    unsigned int   bfSize;           /* Size of file */
    unsigned short bfReserved1;      /* Reserved */
    unsigned short bfReserved2;      /* ... */
    unsigned int   bfOffBits;        /* Offset to bitmap data */
    } BITMAPFILEHEADER;
 
#  define BF_TYPE 0x4D42             /* "MB" */
 
typedef struct                       /**** BMP file info structure ****/
    {
    unsigned int   biSize;           /* Size of info header */
    int            biWidth;          /* Width of image */
    int            biHeight;         /* Height of image */
    unsigned short biPlanes;         /* Number of color planes */
    unsigned short biBitCount;       /* Number of bits per pixel */
    unsigned int   biCompression;    /* Type of compression to use */
    unsigned int   biSizeImage;      /* Size of image data */
    int            biXPelsPerMeter;  /* X pixels per meter */
    int            biYPelsPerMeter;  /* Y pixels per meter */
    unsigned int   biClrUsed;        /* Number of colors used */
    unsigned int   biClrImportant;   /* Number of important colors */
    } BITMAPINFOHEADER;
 
/*
 * Constants for the biCompression field...
 */
 
#  define BI_RGB       0             /* No compression - straight BGR data */
#  define BI_RLE8      1             /* 8-bit run-length compression */
#  define BI_RLE4      2             /* 4-bit run-length compression */
#  define BI_BITFIELDS 3             /* RGB bitmap with RGB masks */
 
typedef struct                       /**** Colormap entry structure ****/
    {
    unsigned char  rgbBlue;          /* Blue value */
    unsigned char  rgbGreen;         /* Green value */
    unsigned char  rgbRed;           /* Red value */
    unsigned char  rgbReserved;      /* Reserved */
    } RGBQUAD;
 
typedef struct                       /**** Bitmap information structure ****/
    {
    BITMAPINFOHEADER bmiHeader;      /* Image header */
    RGBQUAD          bmiColors[256]; /* Image colormap */
    } BITMAPINFO;
#  endif /* !WIN32 */
 
/*
 * Prototypes...
 */
 
extern GLubyte *LoadDIBitmap(const char *filename, BITMAPINFO **info);
extern int     SaveDIBitmap(const char *filename, BITMAPINFO *info,
                            GLubyte *bits);
 
#  ifdef __cplusplus
}
#  endif /* __cplusplus */
#endif /* !_BITMAP_H_ */

 

 

/*
 * Windows BMP file functions for OpenGL.
 *
 * Written by Michael Sweet.
 */
 
#include "bitmap.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
 
 
#ifdef WIN32
/*
 * 'LoadDIBitmap()' - Load a DIB/BMP file from disk.
 *
 * Returns a pointer to the bitmap if successful, NULL otherwise...
 */
 
GLubyte *                          /* O - Bitmap data */
LoadDIBitmap(const char *filename, /* I - File to load */
             BITMAPINFO **info)    /* O - Bitmap information */
    {
    FILE             *fp;          /* Open file pointer */
    GLubyte          *bits;        /* Bitmap pixel bits */
    int              bitsize;      /* Size of bitmap */
    int              infosize;     /* Size of header information */
    BITMAPFILEHEADER header;       /* File header */
 
 
    /* Try opening the file; use "rb" mode to read this *binary* file. */
    if ((fp = fopen(filename, "rb")) == NULL)
        return (NULL);
 
    /* Read the file header and any following bitmap information... */
    if (fread(&header, sizeof(BITMAPFILEHEADER), 1, fp) < 1)
        {
        /* Couldn't read the file header - return NULL... */
        fclose(fp);
        return (NULL);
        }
 
    if (header.bfType != 'MB') /* Check for BM reversed... */
        {
        /* Not a bitmap file - return NULL... */
        fclose(fp);
        return (NULL);
        }
 
    infosize = header.bfOffBits - sizeof(BITMAPFILEHEADER);
    if ((*info = (BITMAPINFO *)malloc(infosize)) == NULL)
        {
        /* Couldn't allocate memory for bitmap info - return NULL... */
        fclose(fp);
        return (NULL);
        }
 
    if (fread(*info, 1, infosize, fp) < infosize)
        {
        /* Couldn't read the bitmap header - return NULL... */
        free(*info);
        fclose(fp);
        return (NULL);
        }
 
    /* Now that we have all the header info read in, allocate memory for *
     * the bitmap and read *it* in...                                    */
    if ((bitsize = (*info)->bmiHeader.biSizeImage) == 0)
        bitsize = ((*info)->bmiHeader.biWidth *
                   (*info)->bmiHeader.biBitCount + 7) / 8 *
                   abs((*info)->bmiHeader.biHeight);
 
    if ((bits = malloc(bitsize)) == NULL)
        {
        /* Couldn't allocate memory - return NULL! */
        free(*info);
        fclose(fp);
        return (NULL);
        }
 
    if (fread(bits, 1, bitsize, fp) < bitsize)
        {
        /* Couldn't read bitmap - free memory and return NULL! */
        free(*info);
        free(bits);
        fclose(fp);
        return (NULL);
        }
 
    /* OK, everything went fine - return the allocated bitmap... */
    fclose(fp);
    return (bits);
    }
 
 
/*
 * 'SaveDIBitmap()' - Save a DIB/BMP file to disk.
 *
 * Returns 0 on success or -1 on failure...
 */
 
int                                /* O - 0 = success, -1 = failure */
SaveDIBitmap(const char *filename, /* I - File to load */
             BITMAPINFO *info,     /* I - Bitmap information */
             GLubyte    *bits)     /* I - Bitmap data */
    {
    FILE             *fp;          /* Open file pointer */
    int              size,         /* Size of file */
                     infosize,     /* Size of bitmap info */
                     bitsize;      /* Size of bitmap pixels */
    BITMAPFILEHEADER header;       /* File header */
 
 
    /* Try opening the file; use "wb" mode to write this *binary* file. */
    if ((fp = fopen(filename, "wb")) == NULL)
        return (-1);
 
    /* Figure out the bitmap size */
    if (info->bmiHeader.biSizeImage == 0)
        bitsize = (info->bmiHeader.biWidth *
                  info->bmiHeader.biBitCount + 7) / 8 *
                 abs(info->bmiHeader.biHeight);
    else
        bitsize = info->bmiHeader.biSizeImage;
 
    /* Figure out the header size */
    infosize = sizeof(BITMAPINFOHEADER);
    switch (info->bmiHeader.biCompression)
        {
        case BI_BITFIELDS :
            infosize += 12; /* Add 3 RGB doubleword masks */
            if (info->bmiHeader.biClrUsed == 0)
              break;
        case BI_RGB :
            if (info->bmiHeader.biBitCount > 8 &&
               info->bmiHeader.biClrUsed == 0)
              break;
        case BI_RLE8 :
        case BI_RLE4 :
            if (info->bmiHeader.biClrUsed == 0)
              infosize += (1 << info->bmiHeader.biBitCount) * 4;
            else
              infosize += info->bmiHeader.biClrUsed * 4;
            break;
        }
 
    size = sizeof(BITMAPFILEHEADER) + infosize + bitsize;
 
    /* Write the file header, bitmap information, and bitmap pixel data... */
    header.bfType      = 'MB'; /* Non-portable... sigh */
    header.bfSize      = size;
    header.bfReserved1 = 0;
    header.bfReserved2 = 0;
    header.bfOffBits   = sizeof(BITMAPFILEHEADER) + infosize;
 
    if (fwrite(&header, 1, sizeof(BITMAPFILEHEADER), fp) < sizeof(BITMAPFILEHEADER))
        {
        /* Couldn't write the file header - return... */
        fclose(fp);
        return (-1);
        }
 
    if (fwrite(info, 1, infosize, fp) < infosize)
        {
        /* Couldn't write the bitmap header - return... */
        fclose(fp);
        return (-1);
        }
 
    if (fwrite(bits, 1, bitsize, fp) < bitsize)
        {
        /* Couldn't write the bitmap - return... */
        fclose(fp);
        return (-1);
        }
 
    /* OK, everything went fine - return... */
    fclose(fp);
    return (0);
    }
 
 
#else /* !WIN32 */
/*
 * Functions for reading and writing 16- and 32-bit little-endian integers.
 */
 
static unsigned short read_word(FILE *fp);
static unsigned int   read_dword(FILE *fp);
static int            read_long(FILE *fp);
 
static int            write_word(FILE *fp, unsigned short w);
static int            write_dword(FILE *fp, unsigned int dw);
static int            write_long(FILE *fp, int l);
 
 
/*
 * 'LoadDIBitmap()' - Load a DIB/BMP file from disk.
 *
 * Returns a pointer to the bitmap if successful, NULL otherwise...
 */
 
GLubyte *                          /* O - Bitmap data */
LoadDIBitmap(const char *filename, /* I - File to load */
             BITMAPINFO **info)    /* O - Bitmap information */
    {
    FILE             *fp;          /* Open file pointer */
    GLubyte          *bits;        /* Bitmap pixel bits */
    GLubyte          *ptr;         /* Pointer into bitmap */
    GLubyte          temp;         /* Temporary variable to swap red and blue */
    int              x, y;         /* X and Y position in image */
    int              length;       /* Line length */
    int              bitsize;      /* Size of bitmap */
    int              infosize;     /* Size of header information */
    BITMAPFILEHEADER header;       /* File header */
 
 
    /* Try opening the file; use "rb" mode to read this *binary* file. */
    if ((fp = fopen(filename, "rb")) == NULL)
        return (NULL);
 
    /* Read the file header and any following bitmap information... */
    header.bfType      = read_word(fp);
    header.bfSize      = read_dword(fp);
    header.bfReserved1 = read_word(fp);
    header.bfReserved2 = read_word(fp);
    header.bfOffBits   = read_dword(fp);
 
    if (header.bfType != BF_TYPE) /* Check for BM reversed... */
        {
        /* Not a bitmap file - return NULL... */
        fclose(fp);
        return (NULL);
        }
 
    infosize = header.bfOffBits - 18;
    if ((*info = (BITMAPINFO *)malloc(sizeof(BITMAPINFO))) == NULL)
        {
        /* Couldn't allocate memory for bitmap info - return NULL... */
        fclose(fp);
        return (NULL);
        }
 
    (*info)->bmiHeader.biSize          = read_dword(fp);
    (*info)->bmiHeader.biWidth         = read_long(fp);
    (*info)->bmiHeader.biHeight        = read_long(fp);
    (*info)->bmiHeader.biPlanes        = read_word(fp);
    (*info)->bmiHeader.biBitCount      = read_word(fp);
    (*info)->bmiHeader.biCompression   = read_dword(fp);
    (*info)->bmiHeader.biSizeImage     = read_dword(fp);
    (*info)->bmiHeader.biXPelsPerMeter = read_long(fp);
    (*info)->bmiHeader.biYPelsPerMeter = read_long(fp);
    (*info)->bmiHeader.biClrUsed       = read_dword(fp);
    (*info)->bmiHeader.biClrImportant  = read_dword(fp);
 
    if (infosize > 40)
        if (fread((*info)->bmiColors, infosize - 40, 1, fp) < 1)
            {
            /* Couldn't read the bitmap header - return NULL... */
            free(*info);
            fclose(fp);
            return (NULL);
            }
 
    /* Now that we have all the header info read in, allocate memory for *
     * the bitmap and read *it* in...                                    */
    if ((bitsize = (*info)->bmiHeader.biSizeImage) == 0)
        bitsize = ((*info)->bmiHeader.biWidth *
                   (*info)->bmiHeader.biBitCount + 7) / 8 *
                   abs((*info)->bmiHeader.biHeight);
 
    if ((bits = malloc(bitsize)) == NULL)
        {
        /* Couldn't allocate memory - return NULL! */
        free(*info);
        fclose(fp);
        return (NULL);
        }
 
    if (fread(bits, 1, bitsize, fp) < bitsize)
        {
        /* Couldn't read bitmap - free memory and return NULL! */
        free(*info);
        free(bits);
        fclose(fp);
        return (NULL);
        }
 
    /* Swap red and blue */
    length = ((*info)->bmiHeader.biWidth * 3 + 3) & ~3;
    for (y = 0; y < (*info)->bmiHeader.biHeight; y ++)
        for (ptr = bits + y * length, x = (*info)->bmiHeader.biWidth;
             x > 0;
             x --, ptr += 3)
            {
            temp   = ptr[0];
            ptr[0] = ptr[2];
            ptr[2] = temp;
            }
 
    /* OK, everything went fine - return the allocated bitmap... */
    fclose(fp);
    return (bits);
    }
 
 
/*
 * 'SaveDIBitmap()' - Save a DIB/BMP file to disk.
 *
 * Returns 0 on success or -1 on failure...
 */
 
int                                /* O - 0 = success, -1 = failure */
SaveDIBitmap(const char *filename, /* I - File to load */
             BITMAPINFO *info,     /* I - Bitmap information */
             GLubyte    *bits)     /* I - Bitmap data */
    {
    FILE *fp;                      /* Open file pointer */
    int  size,                     /* Size of file */
         infosize,                 /* Size of bitmap info */
         bitsize;                  /* Size of bitmap pixels */
 
 
    /* Try opening the file; use "wb" mode to write this *binary* file. */
    if ((fp = fopen(filename, "wb")) == NULL)
        return (-1);
 
    /* Figure out the bitmap size */
    if (info->bmiHeader.biSizeImage == 0)
        bitsize = (info->bmiHeader.biWidth *
                  info->bmiHeader.biBitCount + 7) / 8 *
                 abs(info->bmiHeader.biHeight);
    else
        bitsize = info->bmiHeader.biSizeImage;
 
    /* Figure out the header size */
    infosize = sizeof(BITMAPINFOHEADER);
    switch (info->bmiHeader.biCompression)
        {
        case BI_BITFIELDS :
            infosize += 12; /* Add 3 RGB doubleword masks */
            if (info->bmiHeader.biClrUsed == 0)
              break;
        case BI_RGB :
            if (info->bmiHeader.biBitCount > 8 &&
               info->bmiHeader.biClrUsed == 0)
              break;
        case BI_RLE8 :
        case BI_RLE4 :
            if (info->bmiHeader.biClrUsed == 0)
              infosize += (1 << info->bmiHeader.biBitCount) * 4;
            else
              infosize += info->bmiHeader.biClrUsed * 4;
            break;
        }
 
    size = sizeof(BITMAPFILEHEADER) + infosize + bitsize;
 
    /* Write the file header, bitmap information, and bitmap pixel data... */
    write_word(fp, BF_TYPE);        /* bfType */
    write_dword(fp, size);          /* bfSize */
    write_word(fp, 0);              /* bfReserved1 */
    write_word(fp, 0);              /* bfReserved2 */
    write_dword(fp, 18 + infosize); /* bfOffBits */
 
    write_dword(fp, info->bmiHeader.biSize);
    write_long(fp, info->bmiHeader.biWidth);
    write_long(fp, info->bmiHeader.biHeight);
    write_word(fp, info->bmiHeader.biPlanes);
    write_word(fp, info->bmiHeader.biBitCount);
    write_dword(fp, info->bmiHeader.biCompression);
    write_dword(fp, info->bmiHeader.biSizeImage);
    write_long(fp, info->bmiHeader.biXPelsPerMeter);
    write_long(fp, info->bmiHeader.biYPelsPerMeter);
    write_dword(fp, info->bmiHeader.biClrUsed);
    write_dword(fp, info->bmiHeader.biClrImportant);
 
    if (infosize > 40)
        if (fwrite(info->bmiColors, infosize - 40, 1, fp) < 1)
            {
            /* Couldn't write the bitmap header - return... */
            fclose(fp);
            return (-1);
            }
 
    if (fwrite(bits, 1, bitsize, fp) < bitsize)
        {
        /* Couldn't write the bitmap - return... */
        fclose(fp);
        return (-1);
        }
 
    /* OK, everything went fine - return... */
    fclose(fp);
    return (0);
    }
 
 
/*
 * 'read_word()' - Read a 16-bit unsigned integer.
 */
 
static unsigned short     /* O - 16-bit unsigned integer */
read_word(FILE *fp)       /* I - File to read from */
    {
    unsigned char b0, b1; /* Bytes from file */
 
    b0 = getc(fp);
    b1 = getc(fp);
 
    return ((b1 << 8) | b0);
    }
 
 
/*
 * 'read_dword()' - Read a 32-bit unsigned integer.
 */
 
static unsigned int               /* O - 32-bit unsigned integer */
read_dword(FILE *fp)              /* I - File to read from */
    {
    unsigned char b0, b1, b2, b3; /* Bytes from file */
 
    b0 = getc(fp);
    b1 = getc(fp);
    b2 = getc(fp);
    b3 = getc(fp);
 
    return ((((((b3 << 8) | b2) << 8) | b1) << 8) | b0);
    }
 
 
/*
 * 'read_long()' - Read a 32-bit signed integer.
 */
 
static int                        /* O - 32-bit signed integer */
read_long(FILE *fp)               /* I - File to read from */
    {
    unsigned char b0, b1, b2, b3; /* Bytes from file */
 
    b0 = getc(fp);
    b1 = getc(fp);
    b2 = getc(fp);
    b3 = getc(fp);
 
    return ((int)(((((b3 << 8) | b2) << 8) | b1) << 8) | b0);
    }
 
 
/*
 * 'write_word()' - Write a 16-bit unsigned integer.
 */
 
static int                     /* O - 0 on success, -1 on error */
write_word(FILE           *fp, /* I - File to write to */
           unsigned short w)   /* I - Integer to write */
    {
    putc(w, fp);
    return (putc(w >> 8, fp));
    }
 
 
/*
 * 'write_dword()' - Write a 32-bit unsigned integer.
 */
 
static int                    /* O - 0 on success, -1 on error */
write_dword(FILE         *fp, /* I - File to write to */
            unsigned int dw)  /* I - Integer to write */
    {
    putc(dw, fp);
    putc(dw >> 8, fp);
    putc(dw >> 16, fp);
    return (putc(dw >> 24, fp));
    }
 
 
/*
 * 'write_long()' - Write a 32-bit signed integer.
 */
 
static int           /* O - 0 on success, -1 on error */
write_long(FILE *fp, /* I - File to write to */
           int  l)   /* I - Integer to write */
    {
    putc(l, fp);
    putc(l >> 8, fp);
    putc(l >> 16, fp);
    return (putc(l >> 24, fp));
    }
#endif /* WIN32 */
/*
 * OpenGL bitmap viewing demo from Chapter 7.
 *
 * Written by Michael Sweet.
 */
 
/*
 * Include necessary headers.
 */
 
#include "bitmap.h"
#include <GL/glut.h>
 
 
/*
 * Globals...
 */
 
int        Width;       /* Width of window */
int        Height;      /* Height of window */
BITMAPINFO *BitmapInfo; /* Bitmap information */
GLubyte    *BitmapBits; /* Bitmap data */
 
 
/*
 * Functions...
 */
 
void Redraw(void);
void Resize(int width, int height);
 
 
/*
 * 'main()' - Open a window and display a bitmap.
 */
 
int                /* O - Exit status */
main(int  argc,    /* I - Number of command-line arguments */
     char *argv[]) /* I - Command-line arguments */
    {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
    glutInitWindowSize(792, 573);
    glutCreateWindow("Bitmap File Viewer");
    glutReshapeFunc(Resize);
    glutDisplayFunc(Redraw);
 
    if (argc > 1)
        BitmapBits = LoadDIBitmap(argv[1], &BitmapInfo);
    else
        BitmapBits = LoadDIBitmap("mountain.bmp", &BitmapInfo);
 
    glutMainLoop();
    if (BitmapInfo)
        {
        free(BitmapInfo);
        free(BitmapBits);
        }
    return (0);
    }
 
 
/*
 * 'Redraw()' - Redraw the window...
 */
 
void
Redraw(void)
    {
    GLfloat xsize, ysize;     /* Size of image */
    GLfloat xoffset, yoffset; /* Offset of image */
    GLfloat xscale, yscale;   /* Scaling of image */
 
    /* Clear the window to black */
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
 
    if (BitmapInfo)
        {
        xsize = Width;
        ysize = BitmapInfo->bmiHeader.biHeight * xsize /
                BitmapInfo->bmiHeader.biWidth;
        if (ysize > Height)
            {
            ysize = Height;
            xsize = BitmapInfo->bmiHeader.biWidth * ysize /
                    BitmapInfo->bmiHeader.biHeight;
            }
 
        xscale  = xsize / BitmapInfo->bmiHeader.biWidth;
        yscale  = ysize / BitmapInfo->bmiHeader.biHeight;
 
        xoffset = (Width - xsize) * 0.5;
        yoffset = (Height - ysize) * 0.5;
 
        glRasterPos2f(xoffset, yoffset);
        glPixelZoom(xscale, yscale);
 
        glDrawPixels(BitmapInfo->bmiHeader.biWidth,
                     BitmapInfo->bmiHeader.biHeight,
                     GL_BGR_EXT, GL_UNSIGNED_BYTE, BitmapBits);
        }
 
    glFinish();
    }
 
 
/*
 * 'Resize()' - Resize the window...
 */
 
void
Resize(int width,  /* I - Width of window */
       int height) /* I - Height of window */
    {
    /* Save the new width and height */
    Width  = width;
    Height = height;
 
    /* Reset the viewport... */
    glViewport(0, 0, width, height);
 
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, (GLfloat)width, 0.0, (GLfloat)height, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    }