Android中把bitmap存成BMP格式图片的方法
最近的项目,做图片的另存为功能,需要把图片存成jpg,png,bmp。对于jpg和png来说相对简单,android提供了bitmap.compress()方法可以马上解决。但是对于BMP这种格式,没有很好的支持。我花了几天时间在网上找了很久,都没有找到有用的答案,同样也发了疑问,没有合适的解答。
packagecom.test.bitmap; importjava.io.FileNotFoundException; importjava.io.FileOutputStream; importjava.io.IOException; importandroid.app.Activity; importandroid.graphics.Bitmap; importandroid.os.Bundle; importandroid.os.Environment; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.widget.Button; importandroid.widget.ImageView; publicclassMainactivityextendsActivity{ ImageViewimg; @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); Buttonbtn=(Button)findViewById(R.id.sd); img=(ImageView)findViewById(R.id.img1); btn.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewv){ //TODOAuto-generatedmethodstub Viewview=v.getRootView(); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmapbitmap=view.getDrawingCache(); if(bitmap!=null){ //ByteArrayOutputStreambos=newByteArrayOutputStream(); //bitmap.compress(CompressFormat.PNG,90,bos);只能转成PNG、JPEG //byte[]data=bos.toByteArray(); //img.setImageBitmap(BitmapFactory.decodeByteArray(data,0, //data.length)); intw=bitmap.getWidth(),h=bitmap.getHeight(); int[]pixels=newint[w*h]; bitmap.getPixels(pixels,0,w,0,0,w,h); //ByteBufferdst=ByteBuffer.allocate(bitmap.getRowBytes() //*h); //bitmap.copyPixelsToBuffer(dst); //IntBufferdst=IntBuffer.allocate(w*h); //bitmap.copyPixelsToBuffer(dst); byte[]rgb=addBMP_RGB_888(pixels,w,h); byte[]header=addBMPImageHeader(rgb.length); byte[]infos=addBMPImageInfosHeader(w,h); byte[]buffer=newbyte[54+rgb.length]; System.arraycopy(header,0,buffer,0,header.length); System.arraycopy(infos,0,buffer,14,infos.length); System.arraycopy(rgb,0,buffer,54,rgb.length); try{ FileOutputStreamfos=newFileOutputStream(Environment .getExternalStorageDirectory().getPath() +"/hello.bmp"); fos.write(buffer); }catch(FileNotFoundExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } catch(IOExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } } } }); } //BMP文件头 privatebyte[]addBMPImageHeader(intsize){ byte[]buffer=newbyte[14]; buffer[0]=0x42; buffer[1]=0x4D; buffer[2]=(byte)(size>>0); buffer[3]=(byte)(size>>8); buffer[4]=(byte)(size>>16); buffer[5]=(byte)(size>>24); buffer[6]=0x00; buffer[7]=0x00; buffer[8]=0x00; buffer[9]=0x00; buffer[10]=0x36; buffer[11]=0x00; buffer[12]=0x00; buffer[13]=0x00; returnbuffer; } //BMP文件信息头 privatebyte[]addBMPImageInfosHeader(intw,inth){ byte[]buffer=newbyte[40]; buffer[0]=0x28; buffer[1]=0x00; buffer[2]=0x00; buffer[3]=0x00; buffer[4]=(byte)(w>>0); buffer[5]=(byte)(w>>8); buffer[6]=(byte)(w>>16); buffer[7]=(byte)(w>>24); buffer[8]=(byte)(h>>0); buffer[9]=(byte)(h>>8); buffer[10]=(byte)(h>>16); buffer[11]=(byte)(h>>24); buffer[12]=0x01; buffer[13]=0x00; buffer[14]=0x18; buffer[15]=0x00; buffer[16]=0x00; buffer[17]=0x00; buffer[18]=0x00; buffer[19]=0x00; buffer[20]=0x00; buffer[21]=0x00; buffer[22]=0x00; buffer[23]=0x00; buffer[24]=(byte)0xE0; buffer[25]=0x01; buffer[26]=0x00; buffer[27]=0x00; buffer[28]=0x02; buffer[29]=0x03; buffer[30]=0x00; buffer[31]=0x00; buffer[32]=0x00; buffer[33]=0x00; buffer[34]=0x00; buffer[35]=0x00; buffer[36]=0x00; buffer[37]=0x00; buffer[38]=0x00; buffer[39]=0x00; returnbuffer; } privatebyte[]addBMP_RGB_888(int[]b,intw,inth){ intlen=b.length; System.out.println(b.length); byte[]buffer=newbyte[w*h*3]; intoffset=0; for(inti=len-1;i>=w;i-=w){ //DIB文件格式最后一行为第一行,每行按从左到右顺序 intend=i,start=i-w+1; for(intj=start;j<=end;j++){ buffer[offset]=(byte)(b[j]>>0); buffer[offset+1]=(byte)(b[j]>>8); buffer[offset+1]=(byte)(b[j]>>16); offset+=3; } } returnbuffer; } }
但是我按照这种方法使用之后,保存之后的图片与原来的相比会有很大的颜色差距,详细看附件。
所以我就遇到了新的麻烦,下载了一个UltraEdit软件,然后把保存前的bmp图片和保存后的bmp图片使用UltraEdit打开。分析bmp图片的格式。我从网上找到了一份非常好的说明,详细中文版分析请看附件。提供两个网址,关于bmp格式的分析的:
https://www.nhooo.com/article/78186.htm
https://www.nhooo.com/article/78187.htm
图像文件头
1)1-2:(这里的数字代表的是"字",即两个字节,下同)图像文件头。0x4d42='BM',表示是Windows支持的BMP格式。(注意:查ascii表B0x42,M0x4d,bfType为两个字节,B为low字节,M为high字节所以bfType=0x4D42,而不是0x424D,但注意)
2)3-6:整个文件大小。46900000,为00009046h=36934。
3)7-8:保留,必须设置为0。
4)9-10:保留,必须设置为0。
5)11-14:从文件开始到位图数据之间的偏移量(14+40+4*(2^biBitCount))。46000000,为00000046h=70,上面的文件头就是35字=70字节。
位图信息头
6)15-18:位图图信息头长度。
7)19-22:位图宽度,以像素为单位。80000000,为00000080h=128。
8)23-26:位图高度,以像素为单位。90000000,为00000090h=144。
9)27-28:位图的位面数,该值总是1。0100,为0001h=1。
10)29-30:每个像素的位数。有1(单色),4(16色),8(256色),16(64K色,高彩色),24(16M色,真彩色),32(4096M色,增强型真彩色)。1000为0010h=16。
11)31-34:压缩说明:有0(不压缩),1(RLE8,8位RLE压缩),2(RLE4,4位RLE压缩,3(Bitfields,位域存放)。RLE简单地说是采用像素数+像素值的方式进行压缩。T408采用的是位域存放方式,用两个字节表示一个像素,位域分配为r5b6g5。图中03000000为00000003h=3。
12)35-38:用字节数表示的位图数据的大小,该数必须是4的倍数,数值上等于(≥位图宽度的最小的4的倍数)×位图高度×每个像素位数。00900000为00009000h=80×90×2h=36864。
13)39-42:用象素/米表示的水平分辨率。A00F0000为00000FA0h=4000。
14)43-46:用象素/米表示的垂直分辨率。A00F0000为00000FA0h=4000。
15)47-50:位图使用的颜色索引数。设为0的话,则说明使用所有调色板项。
16)51-54:对图象显示有重要影响的颜色索引的数目。如果是0,表示都重要。
这54位信息非常重要,所以我做了一个尝试,验证是否是因为bmp图片的头信息的不同,造成了颜色的偏差呢?实验方法如下:其实很简单,第一步把保存前和保存后的bmp图片使用UltraEdit打开,把保存前的bmp图片的头即前54位的值,复制,粘贴到保存后的图片的头部,这样两张图的头信息都是保存前的那张图片的头。然后ctrl+s一下保存后的那张图片,再打开发现图片并没有变化。第一步,这回做相反的操作,即把保存后的bmp图片的头即前54位的值,复制,粘贴到保存前的图片的头部,发现也是没有变化,说明文件头不是影响颜色差异的原因。
此时只剩下代码中:byte[]rgb=addBMP_RGB_888(pixels,w,h);此处的问题了,进入这个函数,仔细阅读,发现正是存储颜色信息的地方。读下代码发现,原作者的代码有个bug。此处:buffer[offset+1]=(byte)(b[j]>>16);应该是buffer[offset+2]=(byte)(b[j]>>16);即offset应该是+2,而非加1。更正错误之后,重新运行一下,发现保存后的图片颜色恢复正常,与原图片颜色相同,问题解决。
总结:不知道为什么经常遇到一些,网上很常见,但是却找不到合适我的问题的解决办法,很郁闷,网上很多的链接都是指向这个地址,但是进去了却没有更详细的说明。很遗憾,也很懊恼。希望网上能多分享些有用的,可行的解决方案,这对大家都有好处,免得浪费大家的搜索时间。
这里珍惜感谢csdn的那个博主,解决了我的问题。当然,使用别人的代码,也要多思考,不要轻易完全相信。