BMP图存储—代码实现

最近一直在思考存储BMP图像的问题,在网上看的文章对于原理的讲述都很明确,但具体的实现代码方面有所欠缺。这次用Java语言来给大家说一下BMP的存储方式。

在讲解具体格式之前,给大家提一个小建议,因为对于图片的写入也是一个不小的工程,容易出现很多的细节问题,所以我们可以先利用WinHex对我们的目标图像进行转码,之后将我们得到的图像与其再转码后两方对照,更易分析问题所在。

学习前提:

1. writeInt

OutputStream out;
out.write(int a);该方法将该整数写入文件,是存在数据缺少的。它只写入八位数据,a的低八位,24个高位被省略。

所以对于超过255的整数,这样的写法存在问题。在BMP存储的时候,有的时候是四个字节为一个单位,对于int型,我们要将它的四个八位分别拆出来,再依次写入数据。

注意!BMP在存储时,高位放高字节,低位放低字节,所以我们要先写int型的低八位,最后写高八位。

private void writeInt(BufferedOutputStream ops, int t) throws IOException {
		int a = (t >> 24) & 0xff;
		int b = (t >> 16) & 0xff;
		int c = (t >> 8) & 0xff;
		int d = t & 0xff;
		ops.write(d);
		ops.write(c);
		ops.write(b);
		ops.write(a);		
	}

2. writeShort

short型也占用两个字节,同理进行处理。

private void writeShort(BufferedOutputStream ops, short t) throws IOException {
		int c = (t >> 8) & 0xff;
		int d = t & 0xff;
		ops.write(d);
		ops.write(c);	
	}

2. writeColor

这个方法是针对于24位BMP图像,位图数据存储的是实际的像素值。24/8 = 3,所以每一个像素占用三个字节,分别是r,g,b。

我们利用getRGB()方法得到的整数,8(a) 8(b) 8 (g)8(r)。

	private void writeColor(BufferedOutputStream ops, int t) throws IOException {
		int blue = (t>>16)&0xff;//此处写入三个颜色
		int green = (t>>8)&0xff;
		int red = t&0xff;
        ops.write(red);
        ops.write(green);
        ops.write(blue);		
	}

一、 24色位图的存储

(1)存储结构

  1. BMP文件头
  2. 位图信息头
  3. 颜色索引表(对于24色位图,无颜色索引表)
  4. 位图数据

(2)代码实现

  1. BMP文件头代码及部分代码语句详解
public void saveBMP(BufferedOutputStream ops) throws IOException{
		ops.write('B');//0x42 = B				
		ops.write('M');//0x4D = M
		int size = 14 + 40 +height*width*3+(4-width*3%4)*height;	
		writeInt(ops,size);
		writeShort(ops,(short)0);
		writeShort(ops,(short) 0);
		writeInt(ops,54);
	}

int size = 14 + 40 +heightwidth3+(4-width3%4)height;

其为位图文件大小的计算公式:

14:代表BMP文件头的大小
40 :代表位图信息头的大小
heightwidth3:因为一个像素对应3个8位的数据存储(r,g,b三色)所以我们需要×3
(4-width*3%4)*height:位图进行存储的时候每行的数据的长度必须是4的倍数,所以对于不是4的倍数的可以用比特补充(可以用0填充);

附:若有颜色索引表,此处也要加上颜色索引表这部分的大小

witeInt(ops,54);

54代表偏移量: 即从BMP文件头到实际的图像数据所对应的偏移量(BMP文件头+位图信息头+颜色索引表大小)

  1. 位图信息头代码及部分代码语句详解
	public void savebmpInfo(BufferedOutputStream ops) throws IOException{
		writeInt(ops,40);//位图的信息头所占有的字节数
		writeInt(ops,width);
		writeInt(ops,height);
		writeShort(ops,(short)1);//颜色平面数
		witeShort(ops,(short) 24);//说明比特数/像素数,值有1、2、4、8、16、24、32
		writeInt(ops,0);//BI_RGB, 说明本图像不压缩		
		writeInt(ops,size - 54);
		writeInt(ops,0);//水平分辨率,缺省
		writeInt(ops,0);//垂直分辨率,缺省
		writeInt(ops,2);//说明本位图实际使用的颜色索引数,单色位图有两个颜色
		writeInt(ops,2);//说明本位图实际使用的颜色索引数
	}

writeInt(ops,size - 54);

位图图像数据大小,原先我们求的size为位图图像数据大小+偏移量,这里是在原来的值上减去偏移量.

  1. 位图图像数据代码及部分代码语句详解
public void savebmpData(BufferedOutputStream ops) throws IOException{
		int m = 0;
		if(width*3%4>0){
			m = 4 - width*3%4;//m代表补充的字节数,对于这些字节是没有意义的跳过
		}
		int[][] imgData = new int[width][height];
		  for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				imgData[i][j] = image.getRGB(i, j);
			}
		   }
		  int count = 0,gray = 0,sum = 0;
		  byte c = 0;
		 int[] pix =new int[8];
		for(int i = height - 1;i >= 0 ;i--){
			for(int j = 0;j < width;j++){
				int t = imgData[j][i];
				writeColor(ops,t);
			for(int k = 0;k<m;k++){
				ops.write(0);
			}
		}	
	}

与上文的求size相呼应,这里我们再重申一下。

BMP图像要求每行的数据的长度必须是4的倍数,如果不够需要进行比特填充(以0填充),这样可以达到按行的快速存取。

位图数据区的大小不一定是图片宽×图片高×每像素字节数,需要加上比特填充 :

m = 4 - width3%4;//m代表补充的字节数,对于这些字节是没有意义的跳过*

当我们获取到图像的颜色后,需注意:由于位图信息头中的图像高度是正数,所以位图数据在文件中的排列顺序是从左下角到右上角,以行为主序排列的。我们写入的时候要分外注意这一点。

(3)问题及解决

当时在学习这一部分的时候,真的头大,还好有大佬们的文章辅助,理解了大致原理,但是动手写代码起来不知从何下手,这么多数据真的有点恐怖。

后来搜索了一些代码类的文章,看到了大家的思路,主要是根据大类分别创建方法,在方法里进行相应的小部分写入,逻辑清晰,易实现。

我当时主要在求文件的大小,以及图像颜色的写入部分报错严重。文件大小要注意补充的部分以及它具体的公式是怎样的,不能漏掉任意一个部分。颜色问题时写入的顺序从左到右,从下到上,当时没有考虑,总出现数组越界。后来我就把颜色存入数组,在获取数组数据,其实现在想想这一步其实也是没有必要的。

二、 单色位图的存储

(1)存储结构

相比于原先的存储,单色多了一项索引表,相应的也会引起偏移量的变化。

  1. BMP文件头
  2. 位图信息头
  3. 颜色索引表
  4. 位图数据

单色位图只有黑白两色,一个颜色占用四个字节存储,所以颜色索引表的大小为8个字节。
我现在遇到的问题,当我利用电脑自带的画图软件存储的时候,单色位图的颜色为
00 00 00 00 FF FF FF 00

(2)代码实现

public void saveBMP(BufferedOutputStream ops) throws IOException{
		ops.write('B');//0x42 = B				
		ops.write('M');//0x4D = M		
		size = 14 + 40 + 8 + (height*width) / 8;
		writeInt(ops,size);
		writeShort(ops,(short)0);
		writeShort(ops,(short) 0);
		writeInt(ops,62);

	}
	public void savebmpInfo(BufferedOutputStream ops) throws IOException{
		writeInt(ops,40);
		writeInt(ops,width);
		writeInt(ops,height);
		writeShort(ops,(short)1);
		writeShort(ops,(short) 1);//对于单色位图,所做的修改
		writeInt(ops,0);	
		writeInt(ops,size - 62);//单色图像实际数据的大小
		writeInt(ops,0);
		writeInt(ops,0);
		writeInt(ops,2);//说明本位图实际使用的颜色索引数,单色位图有两个颜色
		writeInt(ops,2);//说明本位图实际使用的颜色索引数
	}
	public void saveColor(BufferedOutputStream ops) throws IOException {
		//单色位图只存在黑白两个颜色
		writeShort(ops,(short) 65535);//白色
		writeShort(ops,(short) 65535);//白色
		writeInt(ops,0);//黑色
	}
	/*
	 * 保存BMP位图信息数据部分的方法
	 */
	public void savebmpData(BufferedOutputStream ops) throws IOException{
		int m = 0;
		if(width*3%4>0){
			m = 4 - width*3%4;//m代表补充的字节数,对于这些字节是没有意义的跳过
			System.out.println("m "+m);
		}
		int[][] imgData = new int[width][height];
		  for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				imgData[i][j] = image.getRGB(i, j);
			}
		   }
		  int count = 0,gray = 0,sum = 0;
		  byte c = 0;
		 int[] pix =new int[8];
		for(int i = height - 1;i >= 0 ;i--){
			for(int j = 0;j < width;j++){
				//对于单色位图,一个像素只写一个字节的一位
				int t = imgData[j][i];		
				gray = toGray(t);
				if(gray < 255/2){					
					pix[count++] = 1;
				}else{
					pix[count++] = 0;
				}
				while(count == 8){
					for (int k = 0; k < pix.length; k++) {
						pix[k] = (int) (pix[k]*Math.pow(2.0, (double)(k)));//k+1改为k,没有白条出现
						c = (byte) (c + pix[k]&0xff);
					}
					ops.write(c);
					++sum;
					count = 0;
					c = 0;
				}
			}	
			for(int k = 0;k<m;k++){
				ops.write(0);
			}
		}	
	}
	private void writeShort(BufferedOutputStream ops, short t) throws IOException {
		int c = (t >> 8) & 0xff;
		int d = t & 0xff;
		ops.write(d);
		ops.write(c);	
	}
	private void writeInt(BufferedOutputStream ops, int t) throws IOException {
		int a = (t >> 24) & 0xff;
		int b = (t >> 16) & 0xff;
		int c = (t >> 8) & 0xff;
		int d = t & 0xff;
		ops.write(d);
		ops.write(c);
		ops.write(b);
		ops.write(a);	
	}
	public byte[] divide(int num){
		byte[] bytes = new byte[4];
		bytes[0] = (byte) ((num)&0xff);//整数的低八位
		bytes[1] = (byte) ((num>>8)&0xff);
		bytes[2] = (byte) ((num>>16)&0xff);
		bytes[3] = (byte) ((num>>24)&0xff);
		return bytes;
	}
	public int toGray(int t){
		int r = (t >> 16) & 0xff;
		int g = (t >> 8) & 0xff;
		int b = 0xff & t;
		int gray = (int)(r*0.3+g*0.59+b*0.11);
		return gray;
	}

public void saveColor(BufferedOutputStream ops) throws IOException {
//单色位图只存在黑白两个颜色
writeShort(ops,(short) 65535);//白色
writeShort(ops,(short) 65535);//白色
writeInt(ops,0);//黑色
}

保存颜色索引,遇到的问题我不知道我这样对不对

gray = toGray(t);
单色图像只有黑白两色,直接对彩色图像处理是不易确定黑白值的,所以我们先将其转为灰度图像,是像素点的灰度值在0-255之间这个时候确定一直,转为黑白就比较方便。

pix[k] = (int) (pix[k]Math.pow(2.0, (double)(k)));
c = (byte) (c + pix[k]&0xff);

这里因为用1/8个字节存储一个像素,而我们写入的时候是以一个字节为单位的,所以我们可以每次读八个数据,再将这些数据存在一个大小为8的数组里,再分别把每一位放到字节中。(我在这里有一个思路,有待实现,就是构造一个方法,例如byte.charat[i]就可以把整数i放到字节相应的位上。)

我现在的图像出现的形式还是比较奇怪的,一开始有白条,是因为把k写成了k+1,后来就是转出来的黑白图像奇奇怪怪。

(3)问题及解决

单色图像我会努力的。

二、 其他格式图像的思考

现在主要存在的问题是颜色索引表的问题,对于颜色索引表的创建,以及位图数据里在相应的位置添加对应的颜色索引,还有点模糊,加油加油!!!
其他需要更改的地方:主要就是索引表的变化引起偏移量的变化!!!

OVER!!!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值