/*
Changes by NCR 12/28/01 - see notes @ NCR

Tips 'N Tricks 

  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

     Summary
     Although Java provides several mechanisms for opening
     images, saving them is not one of its strengths. This tip will
     teach you how to save an image in a 24-bit bitmap file. Plus:
     Jean-Pierre provides all the code necessary to write a bitmap
     file from an image object. (1,500 words) 

By Jean-Pierre Dubé 

   This tip complements Java Tip 43, which demonstrated the process of
loading bitmap files in Java applications. This month, I 
follow up with a tutorial on how to save images in 24-bit
bitmap files and a code snip you can use to write a bitmap file from 
an image object. 

The ability to create a bitmap file opens many doors if you're working
in a Microsoft Windows environment. On my last project, for example, I
had to interface Java with Microsoft Access. The Java program allowed
the user to draw a map on the screen. The map was then printed in a
Microsoft Access report. Because Java doesn't support OLE, my only
solution was to create a bitmap file of the map and tell the Microsoft
Access report where to pick it up. If you've ever had to write an
application to send an image to the clipboard, this tip may be of use to
you -- especially if this information is being passed to another Windows
application.

The format of a bitmap file
The bitmap file format supports 4-bit RLE (run length encoding), as well
as 8-bit and 24-bit encoding. Because we're only dealing with the 24-bit
format, let's take a look at the structure of the file.

The bitmap file is divided into three sections. I've laid them out for you
below.

Section 1: Bitmap file header
This header contains information about the type size and layout of the
bitmap file. The structure is as follows (taken from a C language
structure definition):


typedef struct tagBITMAPFILEHEADER {
   UINT bfType;
   DWORD bfSize;
   UINT bfReserved1;
   UINT bfReserved2;
   DWORD bfOffBits;
}BITMAPFILEHEADER;

Here's a description of the code elements from the above listing:

     bfType: Indicates the type of the file and is always set to BM.

     bfSize: Specifies the size of the whole file in bytes.

     bfReserved1: Reserved -- must be set to 0.

     bfReserved2: Reserved -- must be set to 0.

     bfOffBits: Specifies the byte offset from the BitmapFileHeader to the
     start of the image.

Here you've seen that the purpose of the bitmap header is to identify
the bitmap file. Every program that reads bitmap files uses the bitmap
header for file validation. 

Section 2: Bitmap information header
The next header, called the information header, contains all the
properties of the image itself.

Here's how you specify information about the dimension and the color
format of a Windows 3.0 (or higher) device independent bitmap (DIB):


typedef struct tagBITMAPINFOHEADER {
    DWORD biSize;
    LONG  biWidth;
    LONG  biHeight;
    WORD  biPlanes;
    WORD  biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG  biXPelsPerMeter;
    LONG  biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
} BITMAPINFOHEADER;

Each element of the above code listing is described below:

     biSize: Specifies the number of bytes required by the
     BITMAPINFOHEADER structure.

     biWidth: Specifies the width of the bitmap in pixels.

     biHeight: Specifies the height of the bitmap in pixels.

     biPlanes: Specifies the number of planes for the target device. This
     member must be set to 1.

     biBitCount: Specifies the number of bits per pixel. This value must be
     1, 4, 8, or 24.

     biCompression: Specifies the type of compression for a compressed
     bitmap. In a 24-bit format, the variable is set to 0.

     biSizeImage: Specifies the size in bytes of the image. It is valid to set this member to 0 if
     the bitmap is in the BI_RGB format.

     biXPelsPerMeter: Specifies the horizontal resolution, in pixels per meter, of the target device
     for the bitmap. An application can use this value to select a bitmap from a resource group that
     best matches the characteristics of the current device.

     biYPelsPerMeter: Specifies the vertical resolution, in pixels per meter, of the target device for
     the bitmap.

     biClrUsed: Specifies the number of color indexes in the color table actually used by the
     bitmap. If biBitCount is set to 24, biClrUsed specifies the size of the reference color table
     used to optimize performance of Windows color palettes.

     biClrImportant: Specifies the number of color indexes considered 

     important for displaying the bitmap. If this value is 0, all colors are important.

Now all the information needed to create the image has been defined.

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
*

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.

If you play around with this class and find ways to improve it, let me know! My e-mail appears
below, along with my bio. 


   Printer-friendly version |  Mail this to a friend

About the author
Jean-Pierre Dubé is an independent Java consultant. He founded Infocom, registered in 1988. Since
then, Infocom has developed several custom applications ranging from manufacturing, document
management, and large-scale electrical power-line management. He has extensive programming
experience in C, Visual Basic, and most recently Java, which is now the primary language used by
his company. One of Infocom's recent projects is a diagram API that should become available as a
beta release soon. 

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.
*/

package NormsTools;

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

final public class BitmapFile extends Component {

  private final static boolean   Testing = false; //true;    // Debug

  //--- 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 final byte bfType [] = {(byte)'B', (byte)'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 BitmapFile() {
  }

  //------------------------------------------------------------------
  // This is the one method to use!
  public void saveBitmap(String parFilename, Image parImage,
                           int parWidth, int parHeight) {
     try {
        if (Testing)
           System.out.println("Saving image " + parImage 
                                 + " to file:" + parFilename);
        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);
     }
     int lineLen = parWidth * 3;       // NCR added
     pad = ((lineLen % 4) == 0 ? 0 : (4 - (lineLen % 4)) * parHeight);
//     pad = (4 - ((parWidth * 3) % 4)) * parHeight;   // =2400???

     biSizeImage = ((parWidth * parHeight) * 3) + pad;
     bfSize = biSizeImage + BITMAPFILEHEADER_SIZE
                        + BITMAPINFOHEADER_SIZE;
     biWidth = parWidth;
     biHeight = parHeight;
     if(Testing)
        System.out.println("Image size:" + bfSize + ", width:"+biWidth
                          + ", height:"+biHeight + ", pad:" + pad);
     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); //NCR REMOVED> - 1;
//      pad = 4 - ((biWidth * 3) % 4);
      int lineLen = biWidth * 3;       // NCR ADDED these 2 lines
      pad = ((lineLen % 4) == 0 ? 0 : (4 - (lineLen % 4)));
      rowCount = 1;
      padCount = 0;
      rowIndex = size - biWidth -1;    // NCR added -1
      lastRowIndex = rowIndex;

      if(Testing)
         System.out.println("size:"+size + ", pad:" + pad 
                     + ",  lastRowIndex:" + lastRowIndex
                     + ", rowIndex:"+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++;
         } // end for() loop

         //--- Update the size of the file
         bfSize += padCount - pad;
         biSizeImage += padCount - pad;
         if(Testing)
            System.out.println("updated: size:"+bfSize + ", SizeImage:" 
                           + biSizeImage + ", rowIndex:" + rowIndex
                           + ", padCnt:"+padCount);
      }
      catch (Exception wb) {
         wb.printStackTrace ();
         System.out.println("rowIndex: " + rowIndex + ", lastRowIndex:" + lastRowIndex);
      }
   }  
  /*
   * 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);
  }

   /* *****************************************************************
   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 ncompression to be different from 0 to indicate
   compression is present.
   
   Arguments:
       sdir and sfile are the result of the FileDialog()
       getDirectory() and getFile() methods.
   
   Returns:
       Image Object, be sure to check for (Image)null !!!!
   
      And there you have it. This method can be extended easily to read the
      monochrome and 16-color (4-bit) formats. 
   
   */
   public static Image loadbitmap (String sdir, String sfile){
      if (Testing)
         System.out.println("loading:"+sdir+sfile);
      try {
          FileInputStream fs=new FileInputStream(sdir+sfile);
          return loadbitmap(fs);
      }catch(Exception ex) {
           System.err.println("Caught exception in loadbitmap!");
           ex.printStackTrace();
      }
      return null;
   }

   public static Image loadbitmap(InputStream inStr) {

      Image image;
      try {
          int bflen=14;  // 14 byte BITMAPFILEHEADER
          byte bf[]=new byte[bflen];
          inStr.read(bf,0,bflen);
          int bilen=40; // 40-byte BITMAPINFOHEADER
          byte bi[]=new byte[bilen];
          inStr.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;

          int nbisize = (((int)bi[3]&0xff)<<24)
                        | (((int)bi[2]&0xff)<<16)
                        | (((int)bi[1]&0xff)<<8)
                        | (int)bi[0]&0xff;
   
          int nwidth = (((int)bi[7]&0xff)<<24)
                        | (((int)bi[6]&0xff)<<16)
                        | (((int)bi[5]&0xff)<<8)
                        | (int)bi[4]&0xff;
   
          int nheight = (((int)bi[11]&0xff)<<24)
                        | (((int)bi[10]&0xff)<<16)
                        | (((int)bi[9]&0xff)<<8)
                        | (int)bi[8]&0xff;
   
          int nplanes = (((int)bi[13]&0xff)<<8) | (int)bi[12]&0xff;
   
          int nbitcount = (((int)bi[15]&0xff)<<8) | (int)bi[14]&0xff;
   
          // 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];
   
          int nsizeimage = (((int)bi[23]&0xff)<<24)
                        | (((int)bi[22]&0xff)<<16)
                        | (((int)bi[21]&0xff)<<8)
                        | (int)bi[20]&0xff;
   
          int nxpm = (((int)bi[27]&0xff)<<24)
                        | (((int)bi[26]&0xff)<<16)
                        | (((int)bi[25]&0xff)<<8)
                        | (int)bi[24]&0xff;
   
          int nypm = (((int)bi[31]&0xff)<<24)
                        | (((int)bi[30]&0xff)<<16)
                        | (((int)bi[29]&0xff)<<8)
                        | (int)bi[28]&0xff;
   
          int nclrused = (((int)bi[35]&0xff)<<24)
                        | (((int)bi[34]&0xff)<<16)
                        | (((int)bi[33]&0xff)<<8)
                        | (int)bi[32]&0xff;
   
          int nclrimp = (((int)bi[39]&0xff)<<24)
                        | (((int)bi[38]&0xff)<<16)
                        | (((int)bi[37]&0xff)<<8)
                        | (int)bi[36]&0xff;

          if (Testing){
            System.out.println("File type is :"+(char)bf[0]+(char)bf[1]);
            System.out.println("Size of file is :"+nsize);
            System.out.println("Size of bitmapinfoheader is :"+nbisize);
            System.out.println("Width is :"+nwidth);
            System.out.println("Height is :"+nheight);
            System.out.println("Planes is :"+nplanes);
            System.out.println("BitCount is :"+nbitcount);
            System.out.println("Compression is :"+ncompression);
            System.out.println("SizeImage is :"+nsizeimage);
            System.out.println("X-Pixels per meter is :"+nxpm);
            System.out.println("Y-Pixels per meter is :"+nypm);
            System.out.println("Colors used are :"+nclrused);
            System.out.println("Colors important are :"+nclrimp);
          }
   
          // Now check what bit count is. We only do 24 and 8
          if (nbitcount==24)   {
            // No Palatte data for 24-bit format but scan lines are
            // padded out to even 4-byte boundaries.
            int npad = (nsizeimage / nheight) - nwidth * 3;     
//            System.out.println("npad: " + npad + " " + (nsizeimage / nheight) + " " + nwidth);
            int ndata[] = new int [nheight * nwidth];
            byte brgb[] = new byte [( nwidth + npad) * 3 * nheight];
            inStr.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;
                      if (Testing && false) System.out.println("Encoded Color at ("
                               +i+","+j+")is:"+brgb+" (R,G,B)= ("    // was nrgb???
                               +((int)(brgb[2]) & 0xff)+","
                               +((int)brgb[1]&0xff)+","
                               +((int)brgb[0]&0xff)+")");
                     nindex += 3;
               }
                nindex += npad;
            }
         
            image = Toolkit.getDefaultToolkit().createImage(
                      new MemoryImageSource(nwidth, nheight, ndata, 0, nwidth));
   
      }else if (nbitcount == 8)   {
         // 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.
         int nNumColors = 0;
         if (nclrused > 0)       {
             nNumColors = nclrused;
         }   else        {
             nNumColors = (1&0xff)<<nbitcount;
         }
         if (Testing)
            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];
         inStr.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;
               if (Testing) 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;
         if (Testing)
            System.out.println("npad8 is:"+npad8);
      
         int  ndata8[] = new int [nwidth*nheight];
         byte bdata[] = new byte [(nwidth+npad8)*nheight];
         inStr.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;         // advance to next bndry
         }
      
         image = Toolkit.getDefaultToolkit().createImage(
                    new MemoryImageSource (nwidth, nheight, ndata8, 0, nwidth));
   
         // If neither of the above, ignore it
         } else    {
            System.err.println("Not a 24-bit or 8-bit Windows Bitmap, aborting...");
            image = (Image)null;
         }
   
          inStr.close();
          return image;
   
      }   catch (Exception e)       {
            System.err.println("Caught exception in loadbitmap!");
            e.printStackTrace();
      }
      return (Image) null;
   } // end loadbitmap()

  //-----------------------------------------------------------------------------
  // Following for testing. Comment out when done
  // Note: MSPhotoEd gets error reading, but others can read and display ok???
/*  public static void main(String[] args) {
      String newBM = "NewBitmap.bmp";
      // TongaHarborw.bmp is 799x600
      Image img = BitmapFile.loadbitmap("D:\\JavaDevelopment\\images\\", "TongaHarbor3.bmp");
//      Image img = BitmapUtils.loadbitmap("", newBM);
      System.out.println("image: " + img + ",  " + img.getWidth(null) + "X" + img.getHeight(null));
      BitmapFile bmf = new BitmapFile();
      bmf.saveBitmap(newBM, img, img.getWidth(null), img.getHeight(null));
      System.out.println("Wrote bitmap as " + newBM);
      System.exit(0);
  } // */
} // end class -----------------------------------------------------------------------------------

/*  Output:
Running: java NormsTools.BitmapFile 

loading:D:\JavaDevelopment\images\TongaHarbor.bmp
File type is :BM
Size of file is :1440054
Size of bitmapinfoheader is :40
Width is :800
Height is :600
Planes is :1
BitCount is :24
Compression is :0
SizeImage is :1440000
X-Pixels per meter is :4740
Y-Pixels per meter is :4740
Colors used are :0
Colors important are :0
sun.awt.windows.WImage@55550d,  800X600
Saving image sun.awt.windows.WImage@55550d to file:NewBitmap.bmp
Image size:1440054, width:800, height:600, pad:0
size:480000, pad:0,  lastRowIndex:479200, rowIndex:479200
updated: size:1440054, SizeImage:1440000, rowIndex:-799, padCnt:0
Wrote bitmap as NewBitmap.bmp

0 error(s)
*/
