首先,对于BMP 格式的图片大家都不感觉到陌生吧。
?
简单的说明下:
?BMP是一种与硬件设备无关的图像文件格式,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此, BMP 文件所占用的空间很大。 BMP 文件的图像深度可选 lbit 、 4bit 、 8bit 及 24bit 。 BMP 文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。?由于 BMP 文件格式是 Windows 环境中交换与图有关的数据的一种标准,因此在 Windows 环境中运行的图形图像软件都支持 BMP 图像格式。
?
?
要解析文件,就必须知道他的文件结构
文件结构
组成
典型的BMP 图像文件由四部分组成:
1 :?位图文件 头数据结构 ,它包含BMP 图像文件的类型、显示内容等信息;
2 : 位图信息数据结构 ,它包含有BMP 图像的宽、高、压缩方法,以及定义颜色等信息;
?? 3: 调色板 ,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24 位的 BMP )就不需要调色板;
???? 4 : 位图数据 ,这部分的内容根据BMP 位图使用的位数不同而不同,在 24 位图中直接使用 RGB ,而其他的小于 24 位的使用调色板中颜色索引值。?
?
?
对应的数据结构
?
1. ? BMP文件头 (14 字节 )
?? BMP文件头数据结构含有 BMP 文件的类型、文件大小和位图起始位置等信息。
其结构定义如下:
???Int??? bfType;?//?位图文件的类型,必须为 ' B '' M '两个字母 (0-1字节 )
?Int??? bfSize;?//?位图文件的大小,以字节为单位 (2-5 字节 )
?usignedshort ?bfReserved1;?//?位图文件保留字,必须为 0(6-7 字节 )
???usignedshort ?bfReserved2;?//?位图文件保留字,必须为 0(8-9 字节 )
?Int??? bfOffBits;?//?位图数据的起始位置,以相对于位图 (10-13 字节 )
???Int??? bfOffBits ? ;???? / ? /?文件头的偏移量表示,以字节为单位
?
?
2 :位图信息头(40 字节 )
BMP 位图信息头数据用于说明位图的尺寸等信息。
Int??Size ;?//?本结构所占用字节数 (14-17 字节 )
Int??image_width ;?//?位图的宽度,以像素为单位 (18-21 字节 )
??int ? ?image_heigh ;?//?位图的高度,以像素为单位 (22-25 字节 )
Int?? Planes;?//?目标设备的级别,必须为 1(26-27 字节 )
??int ? ?n biBitCount;//?每个像素所需的位数,必须是 1( 双色 ),(28-29 字节 ) //?4(16 色 ) , 8(256 色 ) 或 24( 真彩色 ) 之一
??Int? ?biCompression;?//?位图压缩类型,必须是? 0( 不压缩 ),(30-33 字节 ) //?1(BI_RLE8 压缩类型 ) 或 2(BI_RLE4 压缩类型 ) 之一
??Int ? ?n SizeImage;?//?位图的大小,以字节为单位 (34-37 字节 )
??Int?? biXPelsPerMeter;?//?位图水平分辨率,每米像素数 (38-41 字节 )
??Int? ?biYPelsPerMeter;?//?位图垂直分辨率,每米像素数 (42-45 字节 )
??Int? ?biClrUsed;//?位图实际使用的颜色表中的颜色数 (46-49 字节 )
??Int? ?biClrImportant;//?位图显示过程中重要的颜色数 (50-53 字节 )
?
3 :颜色表
颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD 类型的结构,定义一种颜色。
class ?RGBQUAD
{
byte ?rgbBlue;//?蓝色的亮度 ( 值范围为 0-255)
byte ?rgbGreen;?//?绿色的亮度 ( 值范围为 0-255)
byte ?rgbRed;?//?红色的亮度 ( 值范围为 0-255)
byte ?rgbReserved;//?保留,必须为 0
}
颜色表中RGBQUAD 结构数据的个数有 biBitCount 来确定 :
当biBitCount=1,4,8 时,分别有 2,16,256 个表项 ;
当biBitCount=24 时,没有颜色表项。
位图信息头和颜色表组成位图信息,
BITMAPINFO结构定义如下 :
class ?BITMAPINFO
{
BITMAPINFOHEADER?bmiHeader;?//?位图信息头
RGBQUAD?bmiColors[1];?//?颜色表
}?
?
?
4 :位图数据
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右, 扫描行之间是从下到上。
位图的一个像素值所占的字节数:
当biBitCount=1 时, 8 个像素占 1 个字节 ;
当biBitCount=4 时, 2 个像素占 1 个字节 ;
当biBitCount=8 时, 1 个像素占 1 个字节 ;
当biBitCount=24 时 ,1 个像素占 3 个字节 ;
Windows规定一个扫描行所占的字节数必须是 4 的倍数 ( 即以 long 为单位 ), 不足的以 0 填充,
?
具体数据举例: 如某BMP 文件开头: 424D?4690?0000?0000?0000?4600?0000?2800?0000?8000?0000?9000?0000?0100*1000?0300?0000
?0090?0000?A00F?0000?A00F?0000?0000?0000?0000?0000*00F8?E007?1F00?0000*02F1?84F1?
04F1?84F1?84F1?06F2?84F1?06F2?04F2?86F2?06F2?86F2?86F2?....?....
BMP 文件可分为四个部分:位图文件头、位图信息头、彩色板、图像数据阵列,在上图中已用 * 分隔。?
?
-----------------------------------猥琐分割线-------------------------------------------------------------
?
比方说,我们就可以做一个BMP图片的查看器
?
1、打开BMP文件时,我们这里选择使用dataInputstream? 读取一个最常见的24位真彩色BMP图片
// 创建文件输入流 java.io.FileInputStream fis = new java.io.FileInputStream(path); // 将文件流包装成一个可以写基本数据类型的输出流 java.io.DataInputStream dis = new java.io.DataInputStream(fis);
?2、读入BMP头文件的基本信息
?
int bflen=14; byte bf[]=new byte[bflen]; dis.read(bf,0,bflen); //读取14字节BMP文件头
?? ?因为看到了BMP头文件中没有说明显示图片重要的信息,我只是开一个BF的数组,把头文件信息读取了出来,不做任何的处理。
?
3、读入位图信息头
int bilen=40; byte bi[]=new byte[bilen]; dis.read(bi,0,bilen);//读取40字节BMP信息头 // 获取一些重要数据 image_width=ChangeInt(bi,7); //源图宽度 System.out.println("宽:"+image_width); image_heigh=ChangeInt(bi,11); //源图高度 System.out.println("高:"+image_heigh); //位数 int nbitcount=(((int)bi[15]&0xff)<<8) | (int)bi[14]&0xff; System.out.println("位数:"+nbitcount); //源图大小 int nsizeimage=ChangeInt(bi,23); System.out.println("源图大小:"+nsizeimage);
?? 由位图信息头中我们也可以看出来,要显示图片,重要的信息也就只有几个,其他都是一些不重要的,我们直接忽略掉。
?
?
因为我是直接读取40位的信息头
所以要将一些byte转为int?? 即ChangeInt
即? 4个byte? ------->? 一个int
?
//转成int public int ChangeInt(byte[] bi,int start){ return (((int)bi[start]&0xff)<<24) | (((int)bi[start-1]&0xff)<<16) | (((int)bi[start-2]&0xff)<<8) | (int)bi[start-3]&0xff; }
因为24为的没有颜色表,所以我们直接读位图数据?
最后,最关键的就是,读取位图数据
?
public void showRGB24(DataInputStream dis) throws IOException{ this.setTitle(path); //弹出一个图片的窗口一个大小 this.setSize(image_width, image_heigh); this.setResizable(false); this.setVisible(true); g=this.getGraphics(); if(!(image_width*3 % 4==0)){//图片的宽度不为0 skip_width =4-image_width*3%4; }//判断是否后面有补0 的情况 //装载RGB颜色的数据数组 imageR = new int[image_heigh][image_width]; imageG = new int[image_heigh][image_width]; imageB = new int[image_heigh][image_width]; //按行读取 如果H,W为正则倒着来 for (int h=image_heigh-1;h>=0;h--){ for (int w=0;w<image_width;w++){ // 读入三原色 int blue = dis.read(); int green = dis.read(); int red = dis.read(); imageB[h][w]=blue; imageG[h][w]=green; imageR[h][w]=red; if(w==0){//跳过补0项 System.out.println(dis.skipBytes(skip_width)); } } } repaint(); }
?关键就是在于? 位图是否有补0
?
有则要跳过,没有就直接读,不然显示出来的BMP图像会倾斜。
?
即注释掉这句话得到的效果
if(w==0){ System.out.println(dis.skipBytes(skip_width)); }
?
?
?
最后paint()中显示就可以看见图片了
?
?
public void paint(java.awt.Graphics g){ for (int h=0;h<image_heigh;h++){ for (int w=0;w<image_width;w++){ g.setColor(new java.awt.Color(imageR[h][w],imageG[h][w],imageB[h][w])); g.fillOval(w, h, 1, 1); } } }
?
?
附上自己的测试代码: