Android:将位图保存为bmp文件格式

我在内存中有一个Bitmap,我需要将它保存在bmp文件中(使用bmp文件格式 )。

有没有办法在Android上做到这一点?

(我读了很多post建议使用png格式 – 这是无损 – 但是,这不是我需要的:我真的需要bmp格式 )。

我已经有一些代码使用Bitmap.compress方法将其保存在jpeg或png中:

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

(我正在回答我自己的问题)

这是我目前的解决方案。 它来自这个来源: https : //github.com/ultrakain/AndroidBitmapUtil (感谢ultrakain和@Francescoverheye)

我只是在计算必须添加到每一行的虚拟字节时修复了一个小错误(这样每行的长度(以字节为单位)是4的倍数(根据bmp格式规范的要求)。

我也做了一些改进以改善表现。

 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; } } 

注意,上面的代码非常慢。 我发现要优化上面的代码,必须先创建一个字节数组来存储所有数据。 当然,我在Xamarin C#工作,所以也许这就是原因。 无论如何,这是我的Xamarin代码,以防任何人有同样的问题。

  public class AndroidBmpUtil { private static int BMP_WIDTH_OF_TIMES = 4; private static 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 byte[] ConvertAndroidBitmapByteArray(Bitmap orgBitmap, String filePath) { if (orgBitmap == null) { return null; } if (filePath == null) { return null; } //image size int width = orgBitmap.Width; int height = orgBitmap.Height; //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; bool 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((sbyte)0x42); buffer.Put((sbyte)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; // This while loop is a lengthy process // Puts take a while so only do one by creating a big array called final byte[] final = new byte[0]; while (row > 0) { // This array is also used to cut down on time of puts byte[] b = new byte[(endPosition - startPosition)*3]; int counter = 0; for (int i = startPosition; i < endPosition; i++) { b[counter] = (byte)((pixels[i] & 0x000000FF)); b[counter + 1] = (byte)((pixels[i] & 0x0000FF00) >> 8); b[counter + 2] = (byte)((pixels[i] & 0x00FF0000) >> 16); counter += 3; } int finalPriorLength = final.Length; Array.Resize(ref final, finalPriorLength + b.Length); Array.Copy(b, 0, final, finalPriorLength, b.Length); if (hasDummy) { finalPriorLength = final.Length; Array.Resize(ref final, finalPriorLength + dummyBytesPerRow.Length); Array.Copy(dummyBytesPerRow, 0, final, finalPriorLength, dummyBytesPerRow.Length); } row--; endPosition = startPosition; startPosition = startPosition - col; } buffer.Put(final); buffer.Rewind(); IntPtr classHandle = JNIEnv.FindClass("java/nio/ByteBuffer"); IntPtr methodId = JNIEnv.GetMethodID(classHandle, "array", "()[B"); IntPtr resultHandle = JNIEnv.CallObjectMethod(buffer.Handle, methodId); byte[] result = JNIEnv.GetArray(resultHandle); JNIEnv.DeleteLocalRef(resultHandle); return result; } /** * Write integer to little-endian * @param value * @return * @throws IOException */ private static byte[] writeInt(int value) { 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) { byte[] b = new byte[2]; b[0] = (byte)(value & 0x00FF); b[1] = (byte)((value & 0xFF00) >> 8); return b; } } } 

要在这里使用它是调用代码:

 using (FileStream outStream = new FileStream(@yourFilePath, FileMode.Create)) { Bitmap Signature = Bitmap.CreateBitmap(user defined values...); byte[] buffer = AndroidBmpUtil.ConvertAndroidBitmapByteArray(Signature, @yourFilePath); // Actually create the file outStream.Write(buffer, 0, buffer.Length); } 

将android Bitmap对象转换为8bit bmp文件的代码。 它来自这个C#项目: https : //www.codeproject.com/articles/70442/c-rgb-to-palette-based-bit-greyscale-bitmap-clas

 import android.graphics.Bitmap; import android.graphics.Color; public class BitmapConvertor { private byte[] Color_palette = new byte[1024]; //a palette containing 256 colors private byte[] BMP_File_Header = new byte[14]; private byte[] DIB_header = new byte[40]; private byte[] Bitmap_Data = null; //returns a byte array of a grey scale bitmap image public byte[] CreateGrayBitmapArray(Bitmap Image) { try { create_parts(Image); //Create the array byte[] bitmap_array = new byte[BMP_File_Header.length + DIB_header.length + Color_palette.length + Bitmap_Data.length]; Copy_to_Index(bitmap_array, BMP_File_Header, 0); Copy_to_Index(bitmap_array, DIB_header, BMP_File_Header.length); Copy_to_Index(bitmap_array, Color_palette, BMP_File_Header.length + DIB_header.length); Copy_to_Index(bitmap_array, Bitmap_Data, BMP_File_Header.length + DIB_header.length + Color_palette.length); return bitmap_array; } catch (Exception e) { return null; //return a null single byte array if fails } } //creates byte array of 256 color grayscale palette private byte[] create_palette() { byte[] color_palette = new byte[1024]; for (int i = 0; i < 256; i++) { color_palette[i * 4 + 0] = (byte) (i); //bule color_palette[i * 4 + 1] = (byte) (i); //green color_palette[i * 4 + 2] = (byte) (i); //red color_palette[i * 4 + 3] = (byte) 0; //padding } return color_palette; } //adds dtata of Source array to Destinition array at the Index private boolean Copy_to_Index(byte[] destination, byte[] source, int index) { try { for (int i = 0; i < source.length; i++) { destination[i + index] = source[i]; } return true; } catch (Exception e) { return false; } } //create different part of a bitmap file private void create_parts(Bitmap img) { //Create Bitmap Data Bitmap_Data = ConvertToGrayscale(img); //Create Bitmap File Header (populate BMP_File_Header array) Copy_to_Index(BMP_File_Header, new byte[]{(byte) 'B', (byte) 'M'}, 0); //magic number Copy_to_Index(BMP_File_Header, writeInt(BMP_File_Header.length + DIB_header.length + Color_palette.length + Bitmap_Data.length), 2); //file size Copy_to_Index(BMP_File_Header, new byte[]{(byte) 'M', (byte) 'C', (byte) 'A', (byte) 'T'}, 6); //reserved for application generating the bitmap file (not imprtant) Copy_to_Index(BMP_File_Header, writeInt(BMP_File_Header.length + DIB_header.length + Color_palette.length), 10); //bitmap raw data offset //Create DIB Header (populate DIB_header array) Copy_to_Index(DIB_header, writeInt(DIB_header.length), 0); //DIB header length Copy_to_Index(DIB_header, writeInt(((Bitmap) img).getWidth()), 4); //image width Copy_to_Index(DIB_header, writeInt(((Bitmap) img).getHeight()), 8); //image height Copy_to_Index(DIB_header, new byte[]{(byte) 1, (byte) 0}, 12); //color planes. NB Must be set to 1 Copy_to_Index(DIB_header, new byte[]{(byte) 8, (byte) 0}, 14); //bits per pixel Copy_to_Index(DIB_header, writeInt(0), 16); //compression method NB BI_RGB = 0 Copy_to_Index(DIB_header, writeInt(Bitmap_Data.length), 20); //lenght of raw bitmap data Copy_to_Index(DIB_header, writeInt(1000), 24); //horizontal reselution NB not important Copy_to_Index(DIB_header, writeInt(1000), 28); //vertical reselution NB not important Copy_to_Index(DIB_header, writeInt(256), 32); //number of colors in the palette Copy_to_Index(DIB_header, writeInt(0), 36); //number of important colors used NB 0 = all colors are imprtant //Create Color palett Color_palette = create_palette(); } //convert the color pixels of Source image into a grayscale bitmap (raw data) private byte[] ConvertToGrayscale(Bitmap Source) { Bitmap source = (Bitmap) Source; int padding = (source.getWidth() % 4) != 0 ? 4 - (source.getWidth() % 4) : 0; //determine padding needed for bitmap file byte[] bytes = new byte[source.getWidth() * source.getHeight() + padding * source.getHeight()]; //create array to contain bitmap data with paddin for (int y = 0; y < source.getHeight(); y++) { for (int x = 0; x < source.getWidth(); x++) { int pixel = source.getPixel(x, y); int g = (int) (0.3 * Color.red(pixel) + 0.59 * Color.green(pixel) + 0.11 * Color.blue(pixel)); //grayscale shade corresponding to rgb bytes[(source.getHeight() - 1 - y) * source.getWidth() + (source.getHeight() - 1 - y) * padding + x] = (byte) g; } //add the padding for (int i = 0; i < padding; i++) { bytes[(source.getHeight() - y) * source.getWidth() + (source.getHeight() - 1 - y) * padding + i] = (byte) 0; } } return bytes; } /** * Write integer to little-endian * * @param value * @return * @throws IOException */ private byte[] writeInt(int value) { 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; } }