`
xiaozhouzhou
  • 浏览: 13355 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

简单的bmp文件打开与保存

阅读更多
简单的bmp文件打开与保存
          在谈bmp文件的打开、保存之前,我先说说在制作这一软件时的感受。在初次听到用输入输出流制作bmp格式文件的打开和保存时,有点不知所措,不知什么是bmp文件,他的格式是怎样的,我该如何制作一个程序去打开、保存这一文件。于是,就在网上找bmp文件格式的定义,让我感受特别深的是:你要用自己写的软件打开某一东西,必须知道他的协议是什么。你想要别人认同你制作的软件,你也必须定义一个完整规范的协议。协议是作为计算机之间,网络之间或者是软件、文件之间互通的一条通道规则。你不认同遵守协议你就寸步难行,你不制定规范协议,你自己制作的东西也无法得到认同。
下面就谈谈bmp格式的协议:

bmp文件有一个规范的格式:

他由四部分构成:
1、位图文件头(占14个字节):它包含BMP图像的文件类型,位图文件大小,文件头偏移量(偏移量是指从文件当前位置跳到指定位置的大小值)等。
2、位图信息头(占40个字节):包含位图宽高,位图大小,目标设备级别(必须为1),每个像素所需位数(下面用到的是24位),压缩类型等。
3、调色板:这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP文件)就不需要调色板。
4、位图数据:位图数据记录了位图的每个像素值,记录顺序的在扫描行内从左到右,从下到上。当位图像素所需位数为24位时,一个像素占三个字节,即R、G、B三个颜色值(0-255)。


对应的数据结构:
【BMP文件头(14个字节)】:
int bfType;//(0-1字节)位图文件的类型,为'B'、'M'两个字母
int bfSize;//(2-5字节)位图文件大小
int usignedshort bfReserved1;//(6-7字节)位图文件保留字,必须为0
int usignedshort bfReserved2;//(8-9字节)位图文件保留字,必须为0
int bfOffBits;//(10-13字节)文件头的偏移量


【位图信息头(40个字节)】:
int Size;//(14-17字节)本结构所占用的字节数
int image_width;//(18-21字节)位图的宽度,单位为像素
int image_height;//(22-25字节)位图的高度,单位为像素
int Planes;//(26-27字节)目标设备的级别,为1
int biBitCount;//(28-29字节)每个像素所需的位数,必须为1、4、8、24其中一个,分别代表双色、16色,256色和真彩色
int biCompression;//(30-33字节)位图压缩类型,为0
int SizeImage;//(34-37字节)位图大小
int biXPelsPerMeter;//(38-41字节)位图水平分辨率
int biYPelsPerMeter;//(42-45字节)位图垂直分辨率
int biClrUsed;//(46-49字节)位图实际使用的颜色表中的颜色数
int biClrImportant;//(50-53字节)位图显示过程中重要的颜色数

由于我们选择的是24位的BMP图片,所以今天不讨论颜色表的问题。

【位图数据】:
位图数据记录了位图的每一个像素值,记录顺序的在扫描行内从左到右,从下到上。位图的一个像素值所占用的字节数:
当biBitCount=1时,8个像素占1个字节;
当biBitCount=8时,2个像素占1个字节;
当biBitCount=16时,1个像素占1个字节;
当biBitCount=24时,1个像素占3个字节;

【BMP文件读取步骤】:
1.利用DataInputStream流将文件中的信息按一定次序读入
2.获取文件中的重要信息
3.根据信息绘制图像

按一定次序:因为我们在文件中读入的是一个一个的字节,而实际操作需要的最少也是一个int(即四个字节),因此我们需要将byte数组转化成int型。
重要信息:BMP文件有很多信息,但是我们打开、保存该文件所需要的信息也就是那几个关键的数据。比如文件的宽高,文件的颜色数值。

【BMP文件读取的关键代码如下】:
				//输入流InputStream、DataInputStream的实例化
				InputStream in=new FileInputStream(f);
				dis=new DataInputStream(in);
				//读入文件头信息
				int len=14;
				byte[] b=new byte[len];
				dis.read(b);
				//读入位图信息头 
				int len1=40;
				byte[] b1=new byte[len1];
				dis.read(b1);
				//读取重要的数据宽高,调用位转换方法changeInt()
				w=changeInt(b1,7);
				h=changeInt(b1,11);
				//参数的直接传递
				bmp.w=w;  
				bmp.h=h;
				//创建数组用于保存rgb值
				imageR=new int[h][w];
				imageG=new int[h][w];
				imageB=new int[h][w];
				int skip_width=0;
				//计算补零的字节数
				int m=w%4;
				if(m!=0){
					skip_width=4-m;
				}
					/*
				 * 读取颜色数值的方法
				 */
				for(int i=h-1;i>=0;i--){
					for(int j=0;j<w;j++){
						Bmp bp=new Bmp();
						int blue=dis.read();
						int green=dis.read();
						int red=dis.read();
						imageB[i][j]=blue;
						imageG[i][j]=green;
						imageR[i][j]=red;
						//跳过补0字节
						if(j==w-1){
							byte bf3[]=new byte[skip_width];

							dis.read(bf3);
						}
                                            }
                                    }

其中调用的changeInt()方法定义如下:
/*
	 * 将字节转换为整形的方法
	 */
	private int changeInt(byte[] b1, int i) {
		return (((int)b1[i]&0xff)<<24)
				|(((int)b1[i-1]&0xff)<<16)
				|(((int)b1[i-2]&0xff)<<8)
				|(int)b1[i-3]&0xff;
	}

在解释之前,先给大家补一点关于Java的位运算符的知识
【Java位运算符】
按位与   &        两位同时为1,结果才为1,否则为0

            0000 1111 & 1111 0000 = 0000 0000

按位或    |   两位中至少一位为1,结果为1,否则为0

                0000 1111  |  1111 0000  =1111 1111

左移运算符    <<    将一个二进制运算单位的各二进制位全部左移若干位(左边二进制位丢弃,右边补0)
         
1111 0000 <<  2  =  1100 0000

右移运算符    >>    将一个二进制运算单位的各二进制位全部右移若干位(右边二进制位丢弃,左边补0)
          
0000 1111 >>  2  =  0000 0011
【疑问】:
w=changeInt(b1,7);

这句代码的意思是,将b1[7],b16],b1[5],b1[4]四个字节组合成一个Int型数。(((int)b1[i]&0xff)<<24):假如b1[7]中存的是【0000 1111】,现先将b1[7]与0xff进行与运算,然后强制转换为int形,再往前移24位。最后把b1[7],b16],b1[5],b1[4]进行或运算(如9|5=13),即为得到的宽。
int m=w%4;
if(m!=0){
     skip_width=4-m;
}

因系统在保存BMP格式文件时,为了效率,规定图片每行的字节数必须是4的倍数。也就是说,若不为四字节的整数倍时,我们要补上0。所以skip_width为4减去每行的字节数对4的余数。
--------------------------------------------------------------------

【BMP文件的保存关键代码】:
//得到当前窗体的左上顶点在电脑屏幕中的坐标值
			int x=bmp.getLocation().x;
			int y=bmp.getLocation().y;
			//System.out.println("x:"+x+"y:"+y);
			JFileChooser jfc=new JFileChooser();
			jfc.showSaveDialog(null);
			File f=jfc.getSelectedFile();
			//File f1=new File(f.getPath());
			try {
				//实例化输出流
				FileOutputStream out=new FileOutputStream(f);
				DataOutputStream dos=new DataOutputStream(out);
                                    //用于截取当前画板上的图画,并返回一张相应的位图
				BufferedImage bf_image=new Robot().createScreenCapture(new Rectangle(x+10,y+100,bmp.getWidth()-20,bmp.getHeight()-110));
				//得到图片的高度,宽度
				int image_w=bf_image.getWidth();
				int image_h=bf_image.getHeight();
				//文件的大小 
				int image_size=image_w*image_h*3;
				//判断文件的保存是否需要补0
				if(image_w%4!=0){
					image_size+=(image_w%4)*image_h*3;
				}
				//创建数组用于保存颜色RGB值
				byte[] rgbs=new byte[3];
				//创建数组用于补0
				byte[] b0=new byte[image_w%4];
				//实例化一个bmp文件头信息的写入类
				FileHead fh=new FileHead(image_size,54);
				//实例化一个bmp文件位图信息的写入类
				FileMid fm=new FileMid(image_w,image_h);
				//写入以上字节码
				dos.write(fh.data());
				dos.write(fm.getData());
				//获取当前像素的rgb值,并写入
				for(int h=image_h-1;h>=0;h--){
					for(int w=0;w<image_w;w++){
						int rgb=bf_image.getRGB(w, h);
						//将整形数据转换成字节保存,一个像素点为三个字节
						rgbs[0]=(byte)rgb;
						rgb=rgb>>8;
						rgbs[1]=(byte)rgb;
						rgb=rgb>>8;
						rgbs[2]=(byte)rgb;
						//写入rgb字节码
						dos.write(rgbs);
						//判断补零的方法
						if((image_w%4!=0)){
							dos.write(b0);
						}
					}
				}
				//清空并关闭输出流
				dos.flush();
				dos.close();
			} catch (Exception e1) {
				e1.printStackTrace();
			}

【疑问】:
rgbs[0]=(byte)rgb;
rgb=rgb>>8;
rgbs[1]=(byte)rgb;
rgb=rgb>>8;
rgbs[2]=(byte)rgb;

首先把rgb值强制转化为byte(强制转换后rgb值不变),赋给rgbs[0],然后将rgb右移8个位(将右八位抛弃,左边补0),强制转化为byte赋给rgbs[1],如此循环,就把rgb(int型)的值反序存在了rgb[0]、rgb[1]、rgb[2]三个字节中了。
【FileHead和FileMid类】:
public class FileHead {
	//存储头文件信息的数组,头文件大小为14个字节
	private byte[] hf=new byte[14];
	//文件的大小
	private int fsize;
	//头文件偏移量
	private int move;
	public FileHead(int fsize, int move){
		this.fsize=fsize;
		this.move=move;
	
	//头两个字节必写入BM,作为bmp文件的标识
	hf[0]='B';
	hf[1]='M';
	//文件大小的写入
	int value=fsize;
	hf[2]=(byte)value;
	value=value>>8;
	hf[3]=(byte)value;
	value=value>>8;
	hf[4]=(byte)value;
	value=value>>8;
	hf[5]=(byte)value;
	
	//头文件偏移量的写入
	value=move;
	hf[10]=(byte)value;
	value=value>>8;
	hf[11]=(byte)value;
	value=value>>8;
	hf[12]=(byte)value;
	value=value>>8;
	hf[13]=(byte)value;
	}
	//返回数组hf[]
	public byte[] data(){
		return hf;
	}
}

public class FileMid {
	//本结构所占用字节数
	int size=40;
	//目标设备的级别,必须为1
	private int level=1;
	//位图的宽度
	private int image_w;
	//位图的高度
	private int image_h;
	//每个像素所需的位数
	private int count=24;
	//位图压缩类型,这里不压缩为0
	private int compression=0;
	//位图的大小
	private int sizeimage;
	//创建数组
	private byte[] fm=new byte[40];
	public FileMid(int image_w, int image_h) {
		this.image_w=image_w;
		this.image_h=image_h;
		//保存字节占用数
		fm[0]=(byte)size;
		size=size>>8;
		fm[1]=(byte)size;
		size=size>>8;
		fm[2]=(byte)size;
		size=size>>8;
		fm[3]=(byte)size;
		
		//写入位图的宽
		int value=image_w;
		fm[4]=(byte)value;
		value=value>>8;
		fm[5]=(byte)value;
		value=value>>8;
		fm[6]=(byte)value;
		value=value>>8;
		fm[7]=(byte)value;
		
		//写入位图的高
		value=image_h;
		fm[8]=(byte)value;
		value=value>>8;
		fm[9]=(byte)value;
		value=value>>8;
		fm[10]=(byte)value;
		value=value>>8;
		fm[11]=(byte)value;
		
		//写入目标设备的级别
		fm[12]=(byte)level;
		level=level>>8;
		fm[13]=(byte)level;
		
		//写入每个像素所需的位数,24位
		fm[14]=(byte)count;
		count=count>>8;
		fm[15]=(byte)count;
		
		//写入压缩类型,这里为0
		fm[16]=(byte)compression;
		compression=compression>>8;
		fm[17]=(byte)compression;
		compression=compression>>8;
		fm[18]=(byte)compression;
		compression=compression>>8;
		fm[19]=(byte)compression;
		
		//得到位图大小,并写入
		sizeimage=image_w*image_h*3-54;
		if(image_w%4!=0){
			sizeimage+=(image_w%4)*image_h*3;
		}
		fm[20]=(byte)sizeimage;
		sizeimage=sizeimage>>8;
		fm[21]=(byte)sizeimage;
		sizeimage=sizeimage>>8;
		fm[22]=(byte)sizeimage;
		sizeimage=sizeimage>>8;
		fm[23]=(byte)sizeimage;
	}
	//返回数组fm[]
	public byte[] getData(){
		return fm;
	}

}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics