简单的bmp文件打开与保存

[b][align=center][size=large]简单的bmp文件打开与保存[/size][/align][/b] [b]在谈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文件有很多信息,但是我们打开、保存该文件所需要的信息也就是那几个关键的数据。比如文件的宽高,文件的颜色数值。[/b]
[size=medium][b]【BMP文件读取的关键代码如下】:[/b][/size]

//输入流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位运算符】
[b]按位与 & 两位同时为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[/b]【疑问】:
w=changeInt(b1,7);

[b]这句代码的意思是,将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),即为得到的宽。[/b]
int m=w%4;
if(m!=0){
skip_width=4-m;
}

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

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

[b]首先把rgb值强制转化为byte(强制转换后rgb值不变),赋给rgbs[0],然后将rgb右移8个位(将右八位抛弃,左边补0),强制转化为byte赋给rgbs[1],如此循环,就把rgb(int型)的值反序存在了rgb[0]、rgb[1]、rgb[2]三个字节中了。[/b]
【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;
}

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值