Using BMPDecoder class to Load an External BMP File(RLE Compression Support)

k-paint The flash.display.Bitmap class represents display objects that represent bitmap images. These can be images that you load with the flash.display.Loader class, or they can be images that you create with the Bitmap() constructor. Similar to Bitmap class, BMP files decoder class BMPDecoder read bmp file and display (written by munegon), RLE compression be supported. The usage is very simple:

function loadBMPFile( url:String ):void {
  var loader:URLLoader = new URLLoader();
  loader.dataFormat = URLLoaderDataFormat.BINARY;
  loader.addEventListener( Event.COMPLETE, onCompleteLoad );
  loader.load( new URLRequest( url ) );
}
function onCompleteLoad( e:Event ):void {
  var loader:URLLoader = e.target as URLLoader;
  var decoder:BMPDecoder = new BMPDecoder();
  var bd:BitmapData = decoder.decode( loader.data );
}

The following is source code of BMPDecoder class :

function loadBMPFile( url:String ):void {
  var loader:URLLoader = new URLLoader();
  loader.dataFormat = URLLoaderDataFormat.BINARY;
  loader.addEventListener( Event.COMPLETE, onCompleteLoad );
  loader.load( new URLRequest( url ) );
}
function onCompleteLoad( e:Event ):void {
  var loader:URLLoader = e.target as URLLoader;
  var decoder:BMPDecoder = new BMPDecoder();
  var bd:BitmapData = decoder.decode( loader.data );
}

The following is source code of BMPDecoder class : 

package com.voidelement.images {
  import flash.display.BitmapData;
  import flash.errors.IOError;
  import flash.utils.ByteArray;
  import flash.utils.Endian;
  public class BMPDecoder {
    //___________________________________________________________ const
    private const BITMAP_HEADER_TYPE:String = "BM";
    private const BITMAP_FILE_HEADER_SIZE:int = 14;
    private const BITMAP_CORE_HEADER_SIZE:int = 12;
    private const BITMAP_INFO_HEADER_SIZE:int = 40;
    private const COMP_RGB      :int = 0;
    private const COMP_RLE8     :int = 1;
    private const COMP_RLE4     :int = 2;
    private const COMP_BITFIELDS:int = 3;
    private const BIT1 :int = 1;
    private const BIT4 :int = 4;
    private const BIT8 :int = 8;
    private const BIT16:int = 16;
    private const BIT24:int = 24;
    private const BIT32:int = 32;
    //___________________________________________________________ vars
    private var bytes:ByteArray;
    private var palette:Array;
    private var bd:BitmapData;
    private var nFileSize:uint;
    private var nReserved1:uint;
    private var nReserved2:uint;
    private var nOffbits:uint;
    private var nInfoSize:uint;
    private var nWidth:int;
    private var nHeight:int;
    private var nPlains:uint;
    private var nBitsPerPixel:uint;
    private var nCompression:uint;
    private var nSizeImage:uint;
    private var nXPixPerMeter:int;
    private var nYPixPerMeter:int;
    private var nColorUsed:uint;
    private var nColorImportant:uint;
    private var nRMask:uint;
    private var nGMask:uint;
    private var nBMask:uint;
    private var nRPos:uint;
    private var nGPos:uint;
    private var nBPos:uint;
    private var nRMax:uint;
    private var nGMax:uint;
    private var nBMax:uint;
    /**
     * Constructor
     */
    public function BMPDecoder() {
      nRPos = 0;
      nGPos = 0;
      nBPos = 0;
    }
    /**
     * decoder
     *
     * @param BMP file ByteArray
     */
    public function decode( data:ByteArray ):BitmapData {
      bytes = data;
      bytes.endian = Endian.LITTLE_ENDIAN;
      bytes.position = 0;
      readFileHeader();
      nInfoSize = bytes.readUnsignedInt();
      switch ( nInfoSize ) {
        case BITMAP_CORE_HEADER_SIZE:
          readCoreHeader();
          break;
        case BITMAP_INFO_HEADER_SIZE:
          readInfoHeader();
          break;
        default:
          readExtendedInfoHeader();
          break;
      }
      bd = new BitmapData( nWidth, nHeight );
      switch ( nBitsPerPixel ){
        case BIT1:
          readColorPalette();
          decode1BitBMP();
          break;
        case BIT4:
          readColorPalette();
          if ( nCompression == COMP_RLE4 ){
            decode4bitRLE();
          } else {
            decode4BitBMP();
          }
          break;
        case BIT8:
          readColorPalette();
          if ( nCompression == COMP_RLE8 ){
            decode8BitRLE();
          } else {
            decode8BitBMP();
          }
          break;
        case BIT16:
          readBitFields();
          checkColorMask();
          decode16BitBMP();
          break;
        case BIT24:
          decode24BitBMP();
          break;
        case BIT32:
          readBitFields();
          checkColorMask();
          decode32BitBMP();
          break;
        default:
          throw new VerifyError("invalid bits per pixel : " + nBitsPerPixel );
      }
      return bd;
    }
    /**
     * read BITMAP FILE HEADER
     */
    private function readFileHeader():void {
      var fileHeader:ByteArray = new ByteArray();
      fileHeader.endian = Endian.LITTLE_ENDIAN;
      try {
        bytes.readBytes( fileHeader, 0, BITMAP_FILE_HEADER_SIZE );
        if ( fileHeader.readUTFBytes( 2 ) != BITMAP_HEADER_TYPE ){
          throw new VerifyError("invalid bitmap header type");
        }
        nFileSize  = fileHeader.readUnsignedInt();
        nReserved1 = fileHeader.readUnsignedShort();
        nReserved2 = fileHeader.readUnsignedShort();
        nOffbits   = fileHeader.readUnsignedInt();
      } catch ( e:IOError ) {
        throw new VerifyError("invalid file header");
      }
    }
    /**
     * read BITMAP CORE HEADER
     */
    private function readCoreHeader():void {
      var coreHeader:ByteArray = new ByteArray();
      coreHeader.endian = Endian.LITTLE_ENDIAN;
      try {
        bytes.readBytes( coreHeader, 0, BITMAP_CORE_HEADER_SIZE - 4 );
        nWidth  = coreHeader.readShort();
        nHeight = coreHeader.readShort();
        nPlains = coreHeader.readUnsignedShort();
        nBitsPerPixel = coreHeader.readUnsignedShort();
      } catch ( e:IOError ) {
        throw new VerifyError("invalid core header");
      }
    }
    /**
     * read BITMAP INFO HEADER
     */
    private function readInfoHeader():void {
      var infoHeader:ByteArray = new ByteArray();
      infoHeader.endian = Endian.LITTLE_ENDIAN;
      try {
        bytes.readBytes( infoHeader, 0, BITMAP_INFO_HEADER_SIZE - 4 );
        nWidth  = infoHeader.readInt();
        nHeight = infoHeader.readInt();
        nPlains = infoHeader.readUnsignedShort();
        nBitsPerPixel = infoHeader.readUnsignedShort();
        nCompression = infoHeader.readUnsignedInt();
        nSizeImage = infoHeader.readUnsignedInt();
        nXPixPerMeter = infoHeader.readInt();
        nYPixPerMeter = infoHeader.readInt();
        nColorUsed = infoHeader.readUnsignedInt();
        nColorImportant = infoHeader.readUnsignedInt();
      } catch ( e:IOError ) {
        throw new VerifyError("invalid info header");
      }
    }
    /**
     * read the extend info of BITMAP INFO HEADER
     */
    private function readExtendedInfoHeader():void {
      var infoHeader:ByteArray = new ByteArray();
      infoHeader.endian = Endian.LITTLE_ENDIAN;
      try {
        bytes.readBytes( infoHeader, 0, nInfoSize - 4 );
        nWidth  = infoHeader.readInt();
        nHeight = infoHeader.readInt();
        nPlains = infoHeader.readUnsignedShort();
        nBitsPerPixel = infoHeader.readUnsignedShort();
        nCompression = infoHeader.readUnsignedInt();
        nSizeImage = infoHeader.readUnsignedInt();
        nXPixPerMeter = infoHeader.readInt();
        nYPixPerMeter = infoHeader.readInt();
        nColorUsed = infoHeader.readUnsignedInt();
        nColorImportant = infoHeader.readUnsignedInt();
        if ( infoHeader.bytesAvailable >= 4 ) nRMask = infoHeader.readUnsignedInt();
        if ( infoHeader.bytesAvailable >= 4 ) nGMask = infoHeader.readUnsignedInt();
        if ( infoHeader.bytesAvailable >= 4 ) nBMask = infoHeader.readUnsignedInt();
      } catch ( e:IOError ) {
        throw new VerifyError("invalid info header");
      }
    }
    /**
     * read bitfields
     */
    private function readBitFields():void {
      if ( nCompression == COMP_RGB ){
        if ( nBitsPerPixel == BIT16 ){
          // RGB555
          nRMask = 0x00007c00;
          nGMask = 0x000003e0;
          nBMask = 0x0000001f;
        } else {
          //RGB888;
          nRMask = 0x00ff0000;
          nGMask = 0x0000ff00;
          nBMask = 0x000000ff;
        }
      } else if ( ( nCompression == COMP_BITFIELDS ) && ( nInfoSize < 52 ) ){
        try {
          nRMask = bytes.readUnsignedInt();
          nGMask = bytes.readUnsignedInt();
          nBMask = bytes.readUnsignedInt();
        } catch ( e:IOError ) {
          throw new VerifyError("invalid bit fields");
        }
      }
    }
    /**
     * read color palette
     */
    private function readColorPalette():void {
      var i:int;
      var len:int = ( nColorUsed > 0 ) ? nColorUsed : Math.pow( 2, nBitsPerPixel );
      palette = new Array( len );
      for ( i = 0; i < len; ++i ){
        palette[ i ] = bytes.readUnsignedInt();
      }
    }
    /**
     * decode 1 bit BMP
     */
    private function decode1BitBMP():void {
      var x:int;
      var y:int;
      var i:int;
      var col:int;
      var buf:ByteArray = new ByteArray();
      var line:int = nWidth / 8;
      if ( line % 4 > 0 ){
        line = ( ( line / 4 | 0 ) + 1 ) * 4;
      }
      try {
        for ( y = nHeight - 1; y >= 0; --y ){
          buf.length = 0;
          bytes.readBytes( buf, 0, line );
          for ( x = 0; x < nWidth; x += 8 ){
            col = buf.readUnsignedByte();
            for ( i = 0; i < 8; ++i ){
              bd.setPixel( x + i, y, palette[ col >> ( 7 - i ) & 0x01 ] );
            }
          }
        }
      } catch ( e:IOError ) {
        throw new VerifyError("invalid image data");
      }
    }
    /**
     * decode 4bit RLE
     */
    private function decode4bitRLE():void {
      var x:int;
      var y:int;
      var i:int;
      var n:int;
      var col:int;
      var data:uint;
      var buf:ByteArray = new ByteArray();
      try {
        for ( y = nHeight - 1; y >= 0; --y ){
          buf.length = 0;
          while ( bytes.bytesAvailable > 0 ){
            n = bytes.readUnsignedByte();
            if ( n > 0 ){
              // encode data
              data = bytes.readUnsignedByte();
              for ( i = 0; i < n/2; ++i ){
                buf.writeByte( data );
              }
            } else {
              n = bytes.readUnsignedByte();
              if ( n > 0 ){
                // abs mode
                bytes.readBytes( buf, buf.length, n/2 );
                buf.position += n/2;
                if ( n/2 + 1 >> 1 << 1 != n/2 ){
                  bytes.readUnsignedByte();
                }
              } else {
                // EOL
                break;
              }
            }
          }
          buf.position = 0;
          for ( x = 0; x < nWidth; x += 2 ){
            col = buf.readUnsignedByte();
            bd.setPixel( x, y, palette[ col >> 4 ] );
            bd.setPixel( x + 1, y, palette[ col & 0x0f ] );
          }
        }
      } catch ( e:IOError ) {
        throw new VerifyError("invalid image data");
      }
    }
    /**
     * decode 4bit (no Compression)
     */
    private function decode4BitBMP():void {
      var x:int;
      var y:int;
      var i:int;
      var col:int;
      var buf:ByteArray = new ByteArray();
      var line:int = nWidth / 2;
      if ( line % 4 > 0 ){
        line = ( ( line / 4 | 0 ) + 1 ) * 4;
      }
      try {
        for ( y = nHeight - 1; y >= 0; --y ){
          buf.length = 0;
          bytes.readBytes( buf, 0, line );
          for ( x = 0; x < nWidth; x += 2 ){
            col = buf.readUnsignedByte();
            bd.setPixel( x, y, palette[ col >> 4 ] );
            bd.setPixel( x + 1, y, palette[ col & 0x0f ] );
          }
        }
      } catch ( e:IOError ) {
        throw new VerifyError("invalid image data");
      }
    }
    /**
     * decode 8bit( RLE Compression )
     */
    private function decode8BitRLE():void {
      var x:int;
      var y:int;
      var i:int;
      var n:int;
      var col:int;
      var data:uint;
      var buf:ByteArray = new ByteArray();
      try {
        for ( y = nHeight - 1; y >= 0; --y ){
          buf.length = 0;
          while ( bytes.bytesAvailable > 0 ){
            n = bytes.readUnsignedByte();
            if ( n > 0 ){
              // encode data
              data = bytes.readUnsignedByte();
              for ( i = 0; i < n; ++i ){
                buf.writeByte( data );
              }
            } else {
              n = bytes.readUnsignedByte();
              if ( n > 0 ){
                // abs mode data
                bytes.readBytes( buf, buf.length, n );
                buf.position += n;
                if ( n + 1 >> 1 << 1 != n ){
                  bytes.readUnsignedByte();
                }
              } else {
                // EOL
                break;
              }
            }
          }
          buf.position = 0;
          for ( x = 0; x < nWidth; ++x ){
            bd.setPixel( x, y, palette[ buf.readUnsignedByte() ] );
          }
        }
      } catch ( e:IOError ) {
        throw new VerifyError("invalid image data");
      }
    }
    /**
     * decode 8bit(no Compression)
     */
    private function decode8BitBMP():void {
      var x:int;
      var y:int;
      var i:int;
      var col:int;
      var buf:ByteArray = new ByteArray();
      var line:int = nWidth;
      if ( line % 4 > 0 ){
        line = ( ( line / 4 | 0 ) + 1 ) * 4;
      }
      try {
        for ( y = nHeight - 1; y >= 0; --y ){
          buf.length = 0;
          bytes.readBytes( buf, 0, line );
          for ( x = 0; x < nWidth; ++x ){
            bd.setPixel( x, y, palette[ buf.readUnsignedByte() ] );
          }
        }
      } catch ( e:IOError ) {
        throw new VerifyError("invalid image data");
      }
    }
    /**
     * decode 16bit
     */
    private function decode16BitBMP():void {
      var x:int;
      var y:int;
      var col:int;
      try {
        for ( y = nHeight - 1; y >= 0; --y ){
          for ( x = 0; x < nWidth; ++x ){
            col = bytes.readUnsignedShort();
            bd.setPixel( x, y, ( ( ( col & nRMask ) >> nRPos )*0xff/nRMax << 16 ) + ( ( ( col & nGMask ) >> nGPos )*0xff/nGMax << 8 ) + ( ( ( col & nBMask ) >> nBPos )*0xff/nBMax << 0 ) );
          }
        }
      } catch ( e:IOError ) {
        throw new VerifyError("invalid image data");
      }
    }
    /**
     * decode 24bit BMP
     */
    private function decode24BitBMP():void {
      var x:int;
      var y:int;
      var col:int;
      var buf:ByteArray = new ByteArray();
      var line:int = nWidth * 3;
      if ( line % 4 > 0 ){
        line = ( ( line / 4 | 0 ) + 1 ) * 4;
      }
      try {
        for ( y = nHeight - 1; y >= 0; --y ){
          buf.length = 0;
          bytes.readBytes( buf, 0, line );
          for ( x = 0; x < nWidth; ++x ){
            bd.setPixel( x, y, buf.readUnsignedByte() + ( buf.readUnsignedByte() << 8 ) + ( buf.readUnsignedByte() << 16 ) );
          }
        }
      } catch ( e:IOError ) {
        throw new VerifyError("invalid image data");
      }
    }
    /**
     * decode 32bit BMP
     */
    private function decode32BitBMP():void {
      var x:int;
      var y:int;
      var col:int;
      try {
        for ( y = nHeight - 1; y >= 0; --y ){
          for ( x = 0; x < nWidth; ++x ){
            col = bytes.readUnsignedInt();
            bd.setPixel( x, y, ( ( ( col & nRMask ) >> nRPos )*0xff/nRMax << 16 ) + ( ( ( col & nGMask ) >> nGPos )*0xff/nGMax << 8 ) + ( ( ( col & nBMask ) >> nBPos )*0xff/nBMax << 0 ) );
          }
        }
      } catch ( e:IOError ) {
        throw new VerifyError("invalid image data");
      }
    }
    /**
     * check color mask
     */
    private function checkColorMask():void {
      if ( ( nRMask & nGMask ) | ( nGMask & nBMask ) | ( nBMask & nRMask ) ){
        throw new VerifyError("invalid bit fields");
      }
      while ( ( ( nRMask >> nRPos ) & 0x00000001 ) == 0 ){
        nRPos++;
      }
      while ( ( ( nGMask >> nGPos ) & 0x00000001 ) == 0 ){
        nGPos++;
      }
      while ( ( ( nBMask >> nBPos ) & 0x00000001 ) == 0 ){
        nBPos++;
      }
      nRMax = nRMask >> nRPos;
      nGMax = nGMask >> nGPos;
      nBMax = nBMask >> nBPos;
    }
    /**
     * print information
     */
    public function traceInfo():void {
      trace("---- FILE HEADER ----");
      trace("nFileSize: " + nFileSize );
      trace("nReserved1: " + nReserved1 );
      trace("nReserved2: " + nReserved2 );
      trace("nOffbits: " + nOffbits );
      trace("---- INFO HEADER ----");
      trace("nWidth: " + nWidth );
      trace("nHeight: " + nHeight );
      trace("nPlains: " + nPlains );
      trace("nBitsPerPixel: " + nBitsPerPixel );
      if ( nInfoSize >= 40 ){
        trace("nCompression: " + nCompression );
        trace("nSizeImage: " + nSizeImage );
        trace("nXPixPerMeter: " + nXPixPerMeter );
        trace("nYPixPerMeter: " + nYPixPerMeter );
        trace("nColorUsed: " + nColorUsed );
        trace("nColorUsed: " + nColorImportant );
      }
      if ( nInfoSize >= 52 ){
        trace("nRMask: " + nRMask.toString( 2 ) );
        trace("nGMask: " + nGMask.toString( 2 ) );
        trace("nBMask: " + nBMask.toString( 2 ) );
      }
    }
  }
}

Cheers!

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DZone
  • Reddit
  • Technorati
  • StumbleUpon
  • Twitter
RSS Enjoy this Post? Subscribe to Ntt.cc

RSS Feed   RSS Feed     Email Feed  Email Feed Follow us Follow us
You can leave a response, or trackback from your own site.

3 Responses to “Using BMPDecoder class to Load an External BMP File(RLE Compression Support)”

  1. cyberlox says:

    I am relatively new to Flex programming. After searching in vain for a way to display 256-color bitmaps in Flex I came across this excellent bitmap decoder. Thank you so much for this! It would have cost me a lot of work to decode a.bmp file from scratch.

  2. lildadou says:

    Error on line 263.
    Correction : var line:int = Math.ceil(nWidth / 8);

    Error commited on trac’s libspark.org

  3. Arun says:

    excellent piece of work… but it is’nt working for 4-bit and 16-bit bmp files. where as it is perfevtly working for 24-bit and 8-bit bmp image file. It would be very helpful if you can provide any updates on it.

    Thanks in advance,
    Arun.

Leave a Reply