package ij.plugin; import ij.*; import ij.io.*; import ij.process.*; import java.awt.*; import java.io.*; import java.awt.image.*; /** Implements the File/Save As/BMP command. Based on BMPFile class from http://www.javaworld.com/javaworld/javatips/jw-javatip60-p2.html */ public class BMP_Writer implements PlugIn { //--- 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 [] = {(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 padWidth = 0; private int biHeight = 0; private int biPlanes = 1; private int biBitCount = 24; private int biCompression = 0; private int biSizeImage = 0; private int biXPelsPerMeter = 0x0; private int biYPelsPerMeter = 0x0; private int biClrUsed = 0; private int biClrImportant = 0; //--- Bitmap raw data private int intBitmap []; private byte byteBitmap []; //--- File section private FileOutputStream fo; private BufferedOutputStream bfo; ImagePlus imp; public void run(String path) { IJ.showProgress(0); imp = WindowManager.getCurrentImage(); if (imp==null) {IJ.noImage(); return;} try { writeImage(imp, path); } catch (Exception e) { String msg = e.getMessage(); if (msg==null || msg.equals("")) msg = ""+e; IJ.error("BMP Writer", "An error occured writing the file.\n \n" + msg); } IJ.showProgress(1); IJ.showStatus(""); } void writeImage(ImagePlus imp, String path) throws Exception { if(imp.getBitDepth()==24) biBitCount = 24; else { biBitCount = 8; LookUpTable lut = imp.createLut(); biClrUsed=lut.getMapSize(); // 8 bit color image may use less bfOffBits+=biClrUsed*4; } if (path==null || path.equals("")) { String prompt = "Save as " + biBitCount + " bit BMP"; SaveDialog sd = new SaveDialog(prompt, imp.getTitle(), ".bmp"); if(sd.getFileName()==null) return; path = sd.getDirectory()+sd.getFileName(); } imp.startTiming(); saveBitmap (path, imp.getImage(), imp.getWidth(), imp.getHeight() ); } public void saveBitmap (String parFilename, Image parImage, int parWidth, int parHeight) throws Exception { fo = new FileOutputStream (parFilename); bfo = new BufferedOutputStream(fo); save (parImage, parWidth, parHeight); bfo.close(); fo.close (); } /* * 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) throws Exception { convertImage (parImage, parWidth, parHeight); writeBitmapFileHeader (); writeBitmapInfoHeader (); if(biBitCount == 8) writeBitmapPalette (); writeBitmap (); } private void writeBitmapPalette() throws Exception { LookUpTable lut = imp.createLut(); byte[] g = lut.getGreens(); byte[] r = lut.getReds(); byte[] b = lut.getBlues(); for(int i = 0;i<lut.getMapSize();i++) { bfo.write(b[i]); bfo.write(g[i]); bfo.write(r[i]); bfo.write(0x00); } } /* * 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; if(biBitCount == 24) intBitmap = (int[]) imp.getProcessor().getPixels(); else byteBitmap = (byte[]) imp.getProcessor().convertToByte(true).getPixels(); biWidth = parWidth; biHeight = parHeight; if(biBitCount==24) pad = 4 - ((biWidth * 3) % 4); else pad = 4 - ((biWidth) % 4); if (pad == 4) // <==== Bug correction pad = 0; // <==== Bug correction padWidth = biWidth*(biBitCount==24?3:1)+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 () throws Exception { int value; int i; int pad; byte rgb [] = new byte [3]; if(biBitCount==24) pad = 4 - ((biWidth * 3) % 4); else pad = 4 - ((biWidth) % 4); if (pad == 4) // <==== Bug correction pad = 0; // <==== Bug correction int counter=0; for(int row = biHeight; row>0; row--) { if (row%20==0) IJ.showProgress((double)(biHeight-row)/biHeight); for(int col = 0; col<biWidth; col++) { if(biBitCount==24) { value = intBitmap [(row-1)*biWidth + col ]; rgb [0] = (byte) (value & 0xFF); rgb [1] = (byte) ((value >> 8) & 0xFF); rgb [2] = (byte) ((value >> 16) & 0xFF); bfo.write(rgb); } else bfo.write(byteBitmap [(row-1)*biWidth + col ]); ++counter; } for (i = 1; i <= pad; i++) bfo.write (0x00); counter += pad; } // IJ.write("counter of bytes written = " + counter); } /* * writeBitmapFileHeader writes the bitmap file header to the file. * */ private void writeBitmapFileHeader() throws Exception { fo.write (bfType); // calculate bfSize bfSize = bfOffBits+padWidth*biHeight; fo.write (intToDWord (bfSize)); fo.write (intToWord (bfReserved1)); fo.write (intToWord (bfReserved2)); fo.write (intToDWord (bfOffBits)); // IJ.write("biClrUsed = " + biClrUsed + " bfSize = " + bfSize + " bfOffBits=" + bfOffBits); } /* * * writeBitmapInfoHeader writes the bitmap information header * to the file. * */ private void writeBitmapInfoHeader () throws Exception { 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)); } /* * * 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); } }