Search code examples
javaandroidbitmapbmp

Android : save a Bitmap to bmp file format


I have a Bitmap in memory and I need to save it in a bmp file (using the bmp file format).

Is there any way to do it on Android ?

(I read a lot of post suggesting to use the png format - which is loss-less - but, that's not what I need: I really need the bmp format).

I already have some code to save it in jpeg or png using the Bitmap.compress method :

/**
 * Save data to file using format.
 * When format is null : the bitmap will be saved in bmp format
 **/

public void writeBitmapToFile(Bitmap data, File file, Bitmap.CompressFormat format) {
    FileOutputStream os = null;
    try {
        os = new FileOutputStream(file);
        if(format==null){

            //TODO : write data to file using the bmp format

        }else{
            data.compress(format, 100, os); //ok for JPEG and PNG
        }
        os.flush();
    } catch (Exception e) {
        //irrelevant code
    } finally {
        //irrelevant code
    }
}

Solution

  • (I'm answering my own question)

    Here is my current solution. It is derived from this source : https://github.com/ultrakain/AndroidBitmapUtil (thanks to ultrakain and @Francescoverheye )

    I just fix a little bug in computation of the dummy bytes that must be added to each row (so that the length of each row in bytes is a multiple of 4 (as required by the bmp format specifications).

    I also made some changes to improve the performances.

    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    
    import android.graphics.Bitmap;
    import android.util.Log;
    
    public class AndroidBmpUtil {
    
        private static final int BMP_WIDTH_OF_TIMES = 4;
        private static final int BYTE_PER_PIXEL = 3;
    
        /**
         * Android Bitmap Object to Window's v3 24bit Bmp Format File
         * @param orgBitmap
         * @param filePath
         * @return file saved result
         */
        public static boolean save(Bitmap orgBitmap, String filePath) throws IOException {
            long start = System.currentTimeMillis();
            if(orgBitmap == null){
                return false;
            }
    
            if(filePath == null){
                return false;
            }
    
            boolean isSaveSuccess = true;
    
            //image size
            int width = orgBitmap.getWidth();
            int height = orgBitmap.getHeight();
    
            //image dummy data size
            //reason : the amount of bytes per image row must be a multiple of 4 (requirements of bmp format)
            byte[] dummyBytesPerRow = null;
            boolean hasDummy = false;
            int rowWidthInBytes = BYTE_PER_PIXEL * width; //source image width * number of bytes to encode one pixel.
            if(rowWidthInBytes%BMP_WIDTH_OF_TIMES>0){
                hasDummy=true;
                //the number of dummy bytes we need to add on each row
                dummyBytesPerRow = new byte[(BMP_WIDTH_OF_TIMES-(rowWidthInBytes%BMP_WIDTH_OF_TIMES))];
                //just fill an array with the dummy bytes we need to append at the end of each row
                for(int i = 0; i < dummyBytesPerRow.length; i++){
                    dummyBytesPerRow[i] = (byte)0xFF;
                }
            }
    
            //an array to receive the pixels from the source image
            int[] pixels = new int[width * height];
    
            //the number of bytes used in the file to store raw image data (excluding file headers)
            int imageSize = (rowWidthInBytes+(hasDummy?dummyBytesPerRow.length:0)) * height;
            //file headers size
            int imageDataOffset = 0x36; 
    
            //final size of the file
            int fileSize = imageSize + imageDataOffset;
    
            //Android Bitmap Image Data
            orgBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
    
            //ByteArrayOutputStream baos = new ByteArrayOutputStream(fileSize);
            ByteBuffer buffer = ByteBuffer.allocate(fileSize);
    
            /**
             * BITMAP FILE HEADER Write Start
             **/
            buffer.put((byte)0x42);
            buffer.put((byte)0x4D);
    
            //size
            buffer.put(writeInt(fileSize));
    
            //reserved
            buffer.put(writeShort((short)0));
            buffer.put(writeShort((short)0));
    
            //image data start offset
            buffer.put(writeInt(imageDataOffset));
    
            /** BITMAP FILE HEADER Write End */
    
            //*******************************************
    
            /** BITMAP INFO HEADER Write Start */
            //size
            buffer.put(writeInt(0x28));
    
            //width, height
            //if we add 3 dummy bytes per row : it means we add a pixel (and the image width is modified.
            buffer.put(writeInt(width+(hasDummy?(dummyBytesPerRow.length==3?1:0):0)));
            buffer.put(writeInt(height));
    
            //planes
            buffer.put(writeShort((short)1));
    
            //bit count
            buffer.put(writeShort((short)24));
    
            //bit compression
            buffer.put(writeInt(0));
    
            //image data size
            buffer.put(writeInt(imageSize));
    
            //horizontal resolution in pixels per meter
            buffer.put(writeInt(0));
    
            //vertical resolution in pixels per meter (unreliable)
            buffer.put(writeInt(0));
    
            buffer.put(writeInt(0));
    
            buffer.put(writeInt(0));
    
            /** BITMAP INFO HEADER Write End */
    
            int row = height;
            int col = width;
            int startPosition = (row - 1) * col;
            int endPosition = row * col;
            while( row > 0 ){
                for(int i = startPosition; i < endPosition; i++ ){
                    buffer.put((byte)(pixels[i] & 0x000000FF));
                    buffer.put((byte)((pixels[i] & 0x0000FF00) >> 8));
                    buffer.put((byte)((pixels[i] & 0x00FF0000) >> 16));
                }
                if(hasDummy){
                    buffer.put(dummyBytesPerRow);
                }
                row--;
                endPosition = startPosition;
                startPosition = startPosition - col;
            }
    
            FileOutputStream fos = new FileOutputStream(filePath);
            fos.write(buffer.array());
            fos.close();
            Log.v("AndroidBmpUtil" ,System.currentTimeMillis()-start+" ms");
    
            return isSaveSuccess;
        }
    
        /**
         * Write integer to little-endian
         * @param value
         * @return
         * @throws IOException
         */
        private static byte[] writeInt(int value) throws IOException {
            byte[] b = new byte[4];
    
            b[0] = (byte)(value & 0x000000FF);
            b[1] = (byte)((value & 0x0000FF00) >> 8);
            b[2] = (byte)((value & 0x00FF0000) >> 16);
            b[3] = (byte)((value & 0xFF000000) >> 24);
    
            return b;
        }
    
        /**
         * Write short to little-endian byte array
         * @param value
         * @return
         * @throws IOException
         */
        private static byte[] writeShort(short value) throws IOException {
            byte[] b = new byte[2];
    
            b[0] = (byte)(value & 0x00FF);
            b[1] = (byte)((value & 0xFF00) >> 8);
    
            return b;
        }
    }