Dirk Bertels

The greatest malfunction of spirit
is to believe things (Louis Pasteur)

Bitmap de-compression

Introduction

Just recently we needed some embedded C code to decompress bitmap files (we use bitmaps in our Retinal camera to display icon-like images on the video screen for such applications as giving user-feedback). I include this code here not because it's anything fancy, but because I couldn't find any on the net. Much can be found on the bitmap format and windows applications that uses standard API to decompress, but nothing for embedded systems.

The bitmap format is used by image files using the .bmp extension. It was developed by Microsoft who uses bitmap images in the windows operating system for storing icons. The bitmap format uses the following structs in its header:

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;		



And here is the code that displays both bitmap and compressed bitmap files. The type of compression used is 8 bit Run Length Encoding (RLE), in which essentially, recurring pixels are stored as a single pixel with a count value. The compression algorithm can be found here. Other information on bitmaps can be found on the same site.

// displays bitmap image top left corner at Xoff, Yoff
int BmpDisplay(const char *Image, int Xoff, int Yoff)
{
  unsigned char * ImageData;         // pointer to array keeping the binary bitmap data
  unsigned int Width;                // width of image
  unsigned int DisplayWidth;         // width of display
  unsigned int Height;               // height of image
  unsigned int Compression;          // stores value of compression                     
  int x,y;                           // coordinates
  
  BITMAPFILEHEADER * BMFH = (BITMAPFILEHEADER *) Image;
  BITMAPINFOHEADER * BMIH = (BITMAPINFOHEADER *) (Image + sizeof(BITMAPFILEHEADER));
  if(8 != SwapBytesShort(BMIH->biBitCount))
  {
    OutStr("biBitCount is %d, should be 8 !!!",SwapBytesShort(BMIH->biBitCount));
    return ERR_BITCOUNT_NOT_8;
  }
  
  // calculate offset to 1st byte of image
  ImageData = (unsigned char *)(Image + SwapBytesInt(BMFH->bfOffBits));  
  DisplayWidth = Width  = SwapBytesInt(BMIH->biWidth);
  Compression = SwapBytesInt(BMIH->biCompression); 
  if (0 != Width & 0x03)
  {
    Width &= ~0x03;
    Width+=4;
  }
  Height = SwapBytesInt(BMIH->biHeight);

  // No compression
  if(0 == Compression)                             
  {
    for(y=Height-1;y>=0;y--)
    {
       for(x=0;x<Width;x++)
       {
         if(x<DisplayWidth)
           plot_pixel(x+Xoff,y+Yoff,*ImageData);
         ImageData++;
       }
    }
  }

  // Bitmap is 8-bit RLE compressed
  else if (1 == Compression)                    
  {
    unsigned char firstByte, secondByte;
    unsigned int currX, currY, i, state;

    currX = Xoff;
    currY = Yoff;
    state = 0;

    while(state == 0)
    {
      firstByte = *ImageData;
      ImageData++;
      if(0 != firstByte)
      {
        secondByte =*ImageData;
        for(i=0; i < firstByte; i++)
        {
          plot_pixel(currX, currY, secondByte);
          currX++;
        }
        ImageData++;
      }
      else   // value is zero
      {
        firstByte = *ImageData;            // store next byte
        ImageData++;
        switch (firstByte)
        {
          case 0:
            currX = Xoff;
            currY++;                       // move cursor to beginning of next line
            break;
          case 1:
            state = ERR_NONE;
            return ERR_NONE;               // end of bitmap, finish plotting
          case 2:
            currX += (int)(*ImageData);    // read byte and add value to x value
            ImageData++;
            currY += (int)(*ImageData);    // read byte and add value to y value
            ImageData++;
            break;
          // for any other value, print the next firstByte bytes
          default:                         
            for(i = 0; i < firstByte; i++)
            {
              secondByte = *ImageData;
              plot_pixel(currX, currY, secondByte);
              ImageData++;
              currX++;
            }
            if(0 != firstByte & 0x01)      // if the run doesn't end on a word boundary, 
              ImageData++;                 // advance the pointer
          } // END switch
        } // END else
      } // END while
    } // END else(if 1 == compression)

  else
  {
     return ERR_COMPRESSION_NOT_SUPPORTED;
  }

  return ERR_NONE;
}