Reading A BMP File In JavaScript
Written by Ian Elliot   
Monday, 19 August 2013
Article Index
Reading A BMP File In JavaScript
Converting Bits To Bitmap
Converting From Bitmap To ImageData
Complete Program Listing

Converting Bits To Bitmap

Now we have the raw data in an ArrayBuffer the first thing to do is convert the bits into a structure i.e. a JavaScript object with the appropriate fields. 

The first thing to do is associate a DataView with the buffer and create an empty object to hold the Bitmap data:

function getBMP(buffer) {
 var datav = new DataView(buffer);
 var bitmap = {};

Next we have to start decoding the BMP format. BMP files always start with two headers - a Bitmap File Header followed by a DIB header. The data follows the headers but there are a number of optional entries that could come after the two headers. 

The format of the Bitmap Header (Wikipedia) is:

OffsetSizePurpose
0 the header field used to identify the BMP & DIB file is 0x42 0x4D in hexadecimal, same as BM in ASCII. The following entries are possible:
  • BM – Windows 3.1x, 95, NT, ... etc.
  • BA – OS/2 struct Bitmap Array
  • CI – OS/2 struct Color Icon
  • CP – OS/2 const Color Pointer
  • IC – OS/2 struct Icon
  • PT – OS/2 Pointer
2
the size of the BMP file in bytes
6
reserved; actual value depends on the application that creates the image
8
reserved; actual value depends on the application that creates the image
10 the offset, i.e. starting address, of the byte where the bitmap image data (pixel array) can be found.

 

As you can see the first field should be BM in ASCII for all of the files you are going to work with. The second is the total file size not the image size and the last field gives the starting location of the pixel data. 

Using the dataview that we have already created it is now easy to convert the bits into the fields of the bitmap object:

bitmap.fileheader = {}; bitmap.fileheader.bfType =
                datav.getUint16(0, true);
bitmap.fileheader.bfSize =
                datav.getUint32(2, true);
bitmap.fileheader.bfReserved1 =
                datav.getUint16(6, true);
bitmap.fileheader.bfReserved2 =
                datav.getUint16(8, true);
bitmap.fileheader.bfOffBits =
                datav.getUint32(10, true);

Notice that the final "endian" parameter has to be true because BMP files are always stored in Little Endian order and this has to be converted into whatever order the machine that the program is running on uses. 

The second header records information about the pixel data and the image. The problem here is that there are a number of possible formats for the header but the one that is used in most cases is the BitMapInfoHeader and its format is (Wikipedia):

 

Offset SizePurpose
14 4 the size of this header (40 bytes)
18 4 the bitmap width in pixels (signed integer).
22 4 the bitmap height in pixels (signed integer).
26 2 the number of color planes being used. Must be set to 1.
28 2 the number of bits per pixel, which is the color depth of the image. Typical values are 1, 4, 8, 16, 24 and 32.
30 4 the compression method being used. See the next table for a list of possible values.
34 4 the image size. This is the size of the raw bitmap data (see below), and should not be confused with the file size.
38 4 the horizontal resolution of the image. (pixel per meter, signed integer)
42 4 the vertical resolution of the image. (pixel per meter, signed integer)
46 4 the number of colors in the color palette, or 0 to default to 2n.
50 4 the number of important colors used, or 0 when every color is important; generally ignored.

 

Notice that the first field gives the size of the header and for the type of header we are expecting this should be 40. You need to add a check that it is 40 if there is any chance that you are going to encounter other types of BMP files. Next we have the size of the bit map and a bit later on the number of bits per pixel. In this case we are going to assume that it is 24 and that the pixels are stored in RGB format. BMP files do come in other forms including paletted and it is fairly easy to extend this example to deal with them. Notice that we are going to ignore the compression field as well - having to decompress the pixel data would take us into a completely different sort of problem. 

Converting the raw bits into suitable fields follows the form of the previous header:

 

bitmap.infoheader = {};
bitmap.infoheader.biSize =
                datav.getUint32(14, true);
bitmap.infoheader.biWidth =
                datav.getUint32(18, true);
bitmap.infoheader.biHeight =
                datav.getUint32(22, true);
bitmap.infoheader.biPlanes =
                datav.getUint16(26, true);
bitmap.infoheader.biBitCount =
                datav.getUint16(28, true);
bitmap.infoheader.biCompression =
                datav.getUint32(30, true);
bitmap.infoheader.biSizeImage =
                datav.getUint32(34, true);
bitmap.infoheader.biXPelsPerMeter =
                datav.getUint32(38, true);
bitmap.infoheader.biYPelsPerMeter =
                datav.getUint32(42, true);
bitmap.infoheader.biClrUsed =
                datav.getUint32(46, true);
bitmap.infoheader.biClrImportant =
                datav.getUint32(50, true);

Finally all that is left is to transfer the pixel data. We already know where in the data starts - as its offset is stored in bfOffBits. To make the bits available all we have to do is associate the buffer with a Uint8Array leaving the program that makes use of the bitmap object to do any decoding needed to work with the pixels. An alternative scheme would be to unpack the data into three fields R, G and B. This would make the data easier to use but potentially slower. 

The only other item of information that the bitmap object provides is the stride of the image. The image is stored one row at a time and if you know the number of bytes to a pixel you can work out the number of bytes in the row. The complication is that hardware likes the number of bytes in a row to be a multiple of 4 and so the number of bytes used to store a row sometimes isn't the same as the number of pixels times the bytes per pixel. The number of bytes used to store a row is usually called the stride - because its the number of bytes you have to step over to get the the next row. To make things easier the bitmap object calculates the stride for the user:

  

 var start = bitmap.fileheader.bfOffBits;  bitmap.stride =
  Math.floor((bitmap.infoheader.biBitCount
    *bitmap.infoheader.biWidth +
                            31) / 32) * 4;
 bitmap.pixels =
         new Uint8Array(buffer, start);
 return bitmap;
}

 



Last Updated ( Tuesday, 27 August 2013 )