/** <H> Java Tip 43: How to read 8- and 24-bit Microsoft Windows bitmaps in Java applications</H>
<P>
A step-by-step guide to loading bitmap files in a Java application
<P>
AUTHOR	Jeff West, with John D. Mitchell
<P>
Currently, only GIF and JPEG images are supported by the standard
<code>getImage()</code> method. While Java routines exist for PNG
(Portable Network Graphics) format images, we don't know of any reader
existing for Microsoft Windows bitmap images.  This tip, authored by
Jeff West, provides code for loading Windows bitmap images. 
<em>(1,400 words)</em>
<P>
Windows bitmap files in Java applications is not
officially supported in the current release of Java. But fear not,
there is a way!  This tip will show you how to accomplish this task --
starting with a description of the basic steps involved in reading the
Microsoft Windows file format.
<P>
The Windows DIB (Device-Independent Bitmap) file format is relatively
straightforward.  The DIB format preserves explicit information unlike
the plain bitmap format which is used for storage of the image in RAM.
The problem is that there are so many image format varieties (1, 4, 8,
and 16 bits, among others).  The solution offered in this Java Tip will
address only the 8- and 24-bit flavors.  These two represent the most
widely encountered varieties.
<P>
<A HREF="/javaworld/javatips/jw-javatip42.html">
<IMG ALIGN="right" BORDER="0" width="65" height="15" SRC="/javaworld/icons/b-nexttip.gif" ALT="[Next Tip]"></a>
<A HREF="/javaworld/javatips/jw-javatips.index.html">
<IMG ALIGN="right" BORDER="0" width="71" height="15" SRC="/javaworld/icons/b-tipindex.gif" ALT="[Tip Index]"></a>
<P>
Regardless of the Windows DIB sub-type, the file format always consists
of a 14-byte file header and a 40-byte information header. These two
headers contain information about exactly what is stored in the file
and in what order. Consult the Microsoft Software Development Kit (SDK)
for the exact and precise meaning of each item in the headers. The
remaining file contents vary depending on the data in the information
header.
<P>
Let's look at the two sub-types we're addressing here. The 24-bit
flavor is very simple:  The RGB (Red-Green-Blue) color values (3 bytes,
in BGR order) follow immediately after the information header. However,
each scan line is padded out to an even 4-byte boundary.  This
"padding" is documented (see Microsoft SDK) to be an optimization for
the Windows bitmap drawing APIs.  Also, the bottom scan line is the
first thing encountered in the file -- so the image has to be read
backwards in relation to the usual graphics coordinate systems in which
the positive vector orientation is down and to the right.
<P>
The 8-bit sub-type is complicated by the insertion of color palette
information between the information header and the pixel data.
Therefore, each pixel entry is simply an 8-bit index into the palette
array of 24-bit RGB colors.  Once again, the pixel information is
padded out to an even 4-byte boundary for each scan line.
<P>
Note that the bitmap image-loading method presented here does not
support decompression of compressed bitmap images.  In fact, this
routine does not even look for this possibility!
This routine is sure to generate an exception if a compressed Windows DIB file is
encountered. The compressed Windows DIB format is documented in the Windows
SDK.
<P>
In terms of performance, this routine can read a 24-bit, 640 x 480 file
(approximately 920 kilobytes) in less than 10 seconds on a
486-DX2-66MHz running Microsoft Windows 95.  Performance can be
signficantly enhanced by using <code>BufferedInputStream</code> instead
of <code>FileInputStream</code>.
<P>
The following routine reads either of the two file formats and
generates an <code>Image</code> object.  Comprehensive error and
exception handling is not included below to keep from muddying the
routine further. An unsupported Windows DIB sub-type can always be
converted using the Windows Paint program.
<P>
 loadbitmap() method converted from Windows C code.
 Reads only uncompressed 24- and 8-bit images.  Tested with
 images saved using Microsoft Paint in Windows 95.  If the image
 is not a 24- or 8-bit image, the program refuses to even try.
 I guess one could include 4-bit images by masking the byte
 by first 1100 and then 0011.  I am not really 
 interested in such images.  If a compressed image is attempted,
 the routine will probably fail by generating an IOException.
 Look for variable <B>ncompression</B> to be different from 0 to indicate
 compression is present.
<P>
 Arguments:
     sdir and sfile are the result of the FileDialog()
     getDirectory() and getFile() methods.
<P>
 Returns:
     Image Object, be sure to check for null !!!!
*/

import java.io.*;
import java.awt.*;
import java.awt.image.*;

 public class LoadBitmap {

/** Create a Java Image from the named file.
 * @param sdir, names the directory,
 * ending with the separator character.
 * @param sfile, names the file,
 * @param comp, any Component as ImageObserver. */

   public static Image loadbitmap (String sdir, String sfile, Component comp)
   throws FileNotFoundException
   {
       System.out.println("loading:"+sdir+sfile);
       return loadbitmap (new FileInputStream(sdir+sfile), comp);
   }

/** Create a Java Image from the open stream.
 * @param fs, the open stream.
 * @param comp, any Component as ImageObserver. */

   public static Image loadbitmap (FileInputStream fs, Component comp) {

     try { Image image;

     int bflen=14;  // 14 byte BITMAPFILEHEADER
     byte bf[]=new byte[bflen];
     fs.read(bf,0,bflen);

     int bilen=40; // 40-byte BITMAPINFOHEADER
     byte bi[]=new byte[bilen];
     fs.read(bi,0,bilen);

     // Interperet data.

     int nsize = (((int)bf[5]&0xff) << 24) 
  | (((int)bf[4]&0xff) << 16)
  | (((int)bf[3]&0xff) << 8)
  | (int)bf[2]&0xff;

     System.out.println("File type is :"+(char)bf[0]+(char)bf[1]);
     System.out.println("Size of file is :"+nsize);

     int nbisize = (((int)bi[3]&0xff) << 24)
  | (((int)bi[2]&0xff) << 16)
  | (((int)bi[1]&0xff) << 8)
  | (int)bi[0]&0xff;

     System.out.println("Size of bitmapinfoheader is :"+nbisize);

     int nwidth = (((int)bi[7]&0xff) << 24)
  | (((int)bi[6]&0xff) << 16)
  | (((int)bi[5]&0xff) << 8)
  | (int)bi[4]&0xff;

     System.out.println("Width is :"+nwidth);

     int nheight = (((int)bi[11]&0xff) << 24)
  | (((int)bi[10]&0xff) << 16)
  | (((int)bi[9]&0xff) << 8)
  | (int)bi[8]&0xff;

     System.out.println("Height is :"+nheight);

     int nplanes = (((int)bi[13]&0xff) << 8) | (int)bi[12]&0xff;

     System.out.println("Planes is :"+nplanes);

     int nbitcount = (((int)bi[15]&0xff) << 8) | (int)bi[14]&0xff;

     System.out.println("BitCount is :"+nbitcount);

     // Look for non-zero values to indicate compression

     int ncompression = (((int)bi[19]) << 24)
  | (((int)bi[18]) << 16)
  | (((int)bi[17]) << 8)
  | (int)bi[16];

     System.out.println("Compression is :"+ncompression);

     int nsizeimage = (((int)bi[23]&0xff) << 24)
  | (((int)bi[22]&0xff) << 16)
  | (((int)bi[21]&0xff) << 8)
  | (int)bi[20]&0xff;

     System.out.println("SizeImage is :"+nsizeimage);

     int nxpm = (((int)bi[27]&0xff) << 24)
  | (((int)bi[26]&0xff) << 16)
  | (((int)bi[25]&0xff) << 8)
  | (int)bi[24]&0xff;

     System.out.println("X-Pixels per meter is :"+nxpm);

     int nypm = (((int)bi[31]&0xff) << 24)
  | (((int)bi[30]&0xff) << 16)
  | (((int)bi[29]&0xff) << 8)
  | (int)bi[28]&0xff;

     System.out.println("Y-Pixels per meter is :"+nypm);

     int nclrused = (((int)bi[35]&0xff) << 24)
  | (((int)bi[34]&0xff) << 16)
  | (((int)bi[33]&0xff) << 8)
  | (int)bi[32]&0xff;

     System.out.println("Colors used are :"+nclrused);

     int nclrimp = (((int)bi[39]&0xff) << 24)
  | (((int)bi[38]&0xff) << 16)
  | (((int)bi[37]&0xff) << 8)
  | (int)bi[36]&0xff;

     System.out.println("Colors important are :"+nclrimp);

  // No Palatte data for 24-bit format but scan lines are
  // padded out to even 4-byte boundaries.

     if (nbitcount==24) {

  int npad = (nsizeimage / nheight) - nwidth * 3;
  int ndata[] = new int [nheight * nwidth];
  byte brgb[] = new byte [( nwidth + npad) * 3 * nheight];
  fs.read (brgb, 0, (nwidth + npad) * 3 * nheight);
  int nindex = 0;

  for (int j = 0; j  <  nheight; j++) {
      for (int i = 0; i  <  nwidth; i++) {
          ndata [nwidth * (nheight - j - 1) + i] =
              (255&0xff) << 24
              | (((int)brgb[nindex+2]&0xff) << 16)
              | (((int)brgb[nindex+1]&0xff) << 8)
              | (int)brgb[nindex]&0xff;

           System.out.println("Encoded Color at ("
              +i+","+j+")is:"+" (R,G,B)= ("
              +((int)(brgb[2]) & 0xff)+","
              +((int)brgb[1]&0xff)+","
              +((int)brgb[0]&0xff)+")");

           nindex += 3; }
      nindex += npad; }

  image = comp.createImage
      ( new MemoryImageSource (nwidth, nheight,
          ndata, 0, nwidth));
  }

  // Have to determine the number of colors, the clrsused
  // parameter is dominant if it is greater than zero.  If
  // zero, calculate colors based on bitsperpixel.

     else if (nbitcount == 8) {

  int nNumColors = 0;
  if (nclrused  >  0)
      nNumColors = nclrused;

  else
      nNumColors = (1&0xff) << nbitcount;

  System.out.println("The number of Colors is"+nNumColors);

  // Some bitmaps do not have the sizeimage field calculated
  // Ferret out these cases and fix 'em.

  if (nsizeimage == 0) {
      nsizeimage = ((((nwidth*nbitcount)+31) & ~31 )  >>  3);
      nsizeimage *= nheight;

      System.out.println("nsizeimage (backup) is"+nsizeimage);
      }

  // Read the palatte colors.
  int  npalette[] = new int [nNumColors];
  byte bpalette[] = new byte [nNumColors*4];
  fs.read (bpalette, 0, nNumColors*4);
  int nindex8 = 0;
  for (int n = 0; n  <  nNumColors; n++) {

      npalette[n] = (255&0xff) << 24
   | (((int)bpalette[nindex8+2]&0xff) << 16)
   | (((int)bpalette[nindex8+1]&0xff) << 8)
   | (int)bpalette[nindex8]&0xff;

    System.out.println ("Palette Color "+n
   +" is:"+npalette[n]+" (res,R,G,B)= ("
   +((int)(bpalette[nindex8+3]) & 0xff)+","
   +((int)(bpalette[nindex8+2]) & 0xff)+","
   +((int)bpalette[nindex8+1]&0xff)+","
   +((int)bpalette[nindex8]&0xff)+")");
      nindex8 += 4;
      }

  // Read the image data (actually indices into the palette)
  // Scan lines are still padded out to even 4-byte
  // boundaries.

  int npad8 = (nsizeimage / nheight) - nwidth;

  System.out.println("nPad is:"+npad8);

  int  ndata8[] = new int [nwidth*nheight];
  byte bdata[] = new byte [(nwidth+npad8)*nheight];
  fs.read (bdata, 0, (nwidth+npad8)*nheight);
  nindex8 = 0;
  for (int j8 = 0; j8  <  nheight; j8++) {
      for (int i8 = 0; i8  <  nwidth; i8++) {
   ndata8 [nwidth*(nheight-j8-1)+i8] =
       npalette [((int)bdata[nindex8]&0xff)];
   nindex8++; }
      nindex8 += npad8; }

  image = comp.createImage
      ( new MemoryImageSource (nwidth, nheight,
          ndata8, 0, nwidth));
  }

     else {
  System.out.println ("Not a 24-bit or 8-bit Windows Bitmap, aborting...");
  image = (Image)null;
  }

     fs.close();
     return image;
     }
     catch (Exception e) { System.out.println("Caught exception in loadbitmap!"); }
 return (Image) null;
 }

 }
 /* <IMG SRC="/cgi-bin/counter">*/
