Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Java Tip 60: Saving bitmap files in Java

A tutorial -- including all the code you need to write a bitmap file from an image object

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone

Page 2 of 3

Section 3: Image

In the 24-bit format, each pixel in the image is represented by a series of three bytes of RGB stored as BRG. Each scan line is padded to an even 4-byte boundary. To complicate the process a little bit more, the image is stored from bottom to top, meaning that the first scan line is the last scan line in the image. The following figure shows both headers (BITMAPHEADER) and (BITMAPINFOHEADER) and part of the image. Each section is delimited by a vertical bar:

                 
0000000000    4D42   B536   0002   0000   0000   0036   0000 | 0028
0000000020    0000   0107   0000   00E0   0000   0001   0018   0000
0000000040    0000   B500   0002   0EC4   0000   0EC4   0000   0000
0000000060    0000   0000   0000 | FFFF   FFFF   FFFF   FFFF   FFFF
0000000100    FFFF   FFFF   FFFF   FFFF   FFFF   FFFF   FFFF   FFFF
*


Now, on to the code

Now that we know all about the structure of a 24-bit bitmap file, here's what you've been waiting for: the code to write a bitmap file from an image object.

import java.awt.*;
import java.io.*;
import java.awt.image.*;
public class BMPFile extends Component {
  //--- Private constants
  private final static int BITMAPFILEHEADER_SIZE = 14;
  private final static int BITMAPINFOHEADER_SIZE = 40;
  //--- Private variable declaration
  //--- Bitmap file header
  private byte bitmapFileHeader [] = new byte [14];
  private byte bfType [] = {'B', 'M'};
  private int bfSize = 0;
  private int bfReserved1 = 0;
  private int bfReserved2 = 0;
  private int bfOffBits = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE;
  //--- Bitmap info header
  private byte bitmapInfoHeader [] = new byte [40];
  private int biSize = BITMAPINFOHEADER_SIZE;
  private int biWidth = 0;
  private int biHeight = 0;
  private int biPlanes = 1;
  private int biBitCount = 24;
  private int biCompression = 0;
  private int biSizeImage = 0x030000;
  private int biXPelsPerMeter = 0x0;
  private int biYPelsPerMeter = 0x0;
  private int biClrUsed = 0;
  private int biClrImportant = 0;
  //--- Bitmap raw data
  private int bitmap [];
  //--- File section
  private FileOutputStream fo;
  //--- Default constructor
  public BMPFile() {
  }
  public void saveBitmap (String parFilename, Image parImage, int
parWidth, int parHeight) {
     try {
        fo = new FileOutputStream (parFilename);
        save (parImage, parWidth, parHeight);
        fo.close ();        
     }
     catch (Exception saveEx) {
        saveEx.printStackTrace ();
     }
  }
  /*
   *  The saveMethod is the main method of the process. This method
   *  will call the convertImage method to convert the memory image to
   *  a byte array; method writeBitmapFileHeader creates and writes
   *  the bitmap file header; writeBitmapInfoHeader creates the 
   *  information header; and writeBitmap writes the image.
   *
   */
  private void save (Image parImage, int parWidth, int parHeight) {
     try {
        convertImage (parImage, parWidth, parHeight);
        writeBitmapFileHeader ();
        writeBitmapInfoHeader ();
        writeBitmap ();
     }
     catch (Exception saveEx) {
        saveEx.printStackTrace ();
     }
  }
  /*
   * convertImage converts the memory image to the bitmap format (BRG).
   * It also computes some information for the bitmap info header.
   *
   */
  private boolean convertImage (Image parImage, int parWidth, int parHeight) {
     int pad;
     bitmap = new int [parWidth * parHeight];
     PixelGrabber pg = new PixelGrabber (parImage, 0, 0, parWidth, parHeight,
                                         bitmap, 0, parWidth);
     try {
        pg.grabPixels ();
     }
     catch (InterruptedException e) {
        e.printStackTrace ();
        return (false);
     }
     pad = (4 - ((parWidth * 3) % 4)) * parHeight;
     biSizeImage = ((parWidth * parHeight) * 3) + pad;
     bfSize = biSizeImage + BITMAPFILEHEADER_SIZE +
BITMAPINFOHEADER_SIZE;
     biWidth = parWidth;
     biHeight = parHeight;
     return (true);
  }
  /*
   * writeBitmap converts the image returned from the pixel grabber to
   * the format required. Remember: scan lines are inverted in
   * a bitmap file!
   *
   * Each scan line must be padded to an even 4-byte boundary.
   */
  private void writeBitmap () {
      int size;
      int value;
      int j;
      int i;
      int rowCount;
      int rowIndex;
      int lastRowIndex;
      int pad;
      int padCount;
      byte rgb [] = new byte [3];
      size = (biWidth * biHeight) - 1;
      pad = 4 - ((biWidth * 3) % 4);
      if (pad == 4)   // <==== Bug correction
         pad = 0;     // <==== Bug correction
      rowCount = 1;
      padCount = 0;
      rowIndex = size - biWidth;
      lastRowIndex = rowIndex;
      try {
         for (j = 0; j < size; j++) {
            value = bitmap [rowIndex];
            rgb [0] = (byte) (value & 0xFF);
            rgb [1] = (byte) ((value >> 8) & 0xFF);
            rgb [2] = (byte) ((value >>  16) & 0xFF);
            fo.write (rgb);
            if (rowCount == biWidth) {
               padCount += pad;
               for (i = 1; i <= pad; i++) {
                  fo.write (0x00);
               }
               rowCount = 1;
               rowIndex = lastRowIndex - biWidth;
               lastRowIndex = rowIndex;
            }
            else
               rowCount++;
            rowIndex++;
         }
         //--- Update the size of the file
         bfSize += padCount - pad;
         biSizeImage += padCount - pad;
      }
      catch (Exception wb) {
         wb.printStackTrace ();
      }
   }  
  /*
   * writeBitmapFileHeader writes the bitmap file header to the file.
   *
   */
  private void writeBitmapFileHeader () {
     try {
        fo.write (bfType);
        fo.write (intToDWord (bfSize));
        fo.write (intToWord (bfReserved1));
        fo.write (intToWord (bfReserved2));
        fo.write (intToDWord (bfOffBits));
     }
     catch (Exception wbfh) {
        wbfh.printStackTrace ();
     }
  }
  /*
   *
   * writeBitmapInfoHeader writes the bitmap information header
   * to the file.
   *
   */
  private void writeBitmapInfoHeader () {
     try {
        fo.write (intToDWord (biSize));
        fo.write (intToDWord (biWidth));
        fo.write (intToDWord (biHeight));
        fo.write (intToWord (biPlanes));
        fo.write (intToWord (biBitCount));
        fo.write (intToDWord (biCompression));
        fo.write (intToDWord (biSizeImage));
        fo.write (intToDWord (biXPelsPerMeter));
        fo.write (intToDWord (biYPelsPerMeter));
        fo.write (intToDWord (biClrUsed));
        fo.write (intToDWord (biClrImportant));
     }
     catch (Exception wbih) {
        wbih.printStackTrace ();
     }
  }
  /*
   *
   * intToWord converts an int to a word, where the return
   * value is stored in a 2-byte array.
   *
   */
  private byte [] intToWord (int parValue) {
     byte retValue [] = new byte [2];
     retValue [0] = (byte) (parValue & 0x00FF);
     retValue [1] = (byte) ((parValue >>  8) & 0x00FF);
     return (retValue);
  }
  /*
   *
   * intToDWord converts an int to a double word, where the return
   * value is stored in a 4-byte array.
   *
   */
  private byte [] intToDWord (int parValue) {
     byte retValue [] = new byte [4];
     retValue [0] = (byte) (parValue & 0x00FF);
     retValue [1] = (byte) ((parValue >>  8) & 0x000000FF);
     retValue [2] = (byte) ((parValue >>  16) & 0x000000FF);
     retValue [3] = (byte) ((parValue >>  24) & 0x000000FF);
     return (retValue);
  }
}


Conclusion

That's all there is to it. I'm sure you'll find this class very useful, since, as of JDK 1.1.6, Java doesn't support saving images in any of the popular formats. JDK 1.2 will offer support for creating JPEG images, but not support for bitmaps. So this class will still fill a gap in JDK 1.2.

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comments (2)
Login
Forgot your account info?

ye, you absolutly right! There is some bug in code!By Anonymous on February 6, 2010, 2:37 pmye, you absolutly right! There is some bug in code!

Reply | Read entire comment

Does not work with windows image fax viewerBy Anonymous on October 23, 2008, 8:42 amHi, But unfortunately, the BMP drawn using this code does not open up with windows image and fax viewer in Windows XP. Not very sure of the reasons...I am...

Reply | Read entire comment

View all comments

Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources