BMP图像解析
BMP是一种与硬件设备无关的图像文件格式,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此, BMP 文件所占用的空间很大。 BMP 文件的图像深度可选 lbit 、 4bit 、 8bit 及 24bit 。 BMP 文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。 由于 BMP 文件格式是 Windows 环境中交换与图有关的数据的一种标准,因此在 Windows 环境中运行的图形图像软件都支持 BMP 图像格式。
要解析文件,就必须知道他的文件结构:
6.3.1 BMP文件结构
典型的BMP 图像文件由四部分组成:
1 . 位图文件 头数据结构 ,它包含BMP 图像文件的类型、显示内容等信息;
2 .位图信息数据结构 ,它包含有BMP 图像的宽、高、压缩方法,以及定义颜色等信息;
3. 调色板 ,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24 位的 BMP )就不需要调色板;
4. 位图数据 ,这部分的内容根据BMP 位图使用的位数不同而不同,在 24 位图中直接使用 RGB ,而其他的小于 24 位的使用调色板中颜色索引值。
6.3.2对应的数据结构
① BMP文件头 (14 字节 )
BMP文件头数据结构含有 BMP 文件的类型、文件大小和位图起始位置等信息。其结构定义如下:
int bfType; // 位图文件的类型,必须为 ' B '' M '两个字母 (0-1字节 ) int bfSize; // 位图文件的大小,以字节为单位 (2-5 字节 ) int bfReserved1; // 位图文件保留字,必须为 0(6-7 字节 ) int bfReserved2; // 位图文件保留字,必须为 0(8-9 字节 ) int bfOffBits; // 位图数据的起始位置,以相对于位图 (10-13 字节 ) |
② 位图信息头(40 字节 )
BMP 位图信息头数据用于说明位图的尺寸等信息。
int Size; // 本结构所占用字节数 (14-17 字节 ) int image_width; // 位图的宽度,以像素为单位 (18-21 字节 ) int image_heigh; // 位图的高度,以像素为单位 (22-25 字节 ) int Planes; // 目标设备的级别,必须为 1(26-27 字节 ) int biBitCount;// 每个像素所需的位数,必须是 1(双色),(28-29 字节) 4(16 色 ) , 8(256 色 ) 或 24(// 真彩色 ) 之一 int biCompression; // 位图压缩类型,必须是 0( 不压缩 ),(30-33 字节 ) 1(BI_RLE8 压缩类型 ) 或// 2(BI_RLE4 压缩类型 ) 之一 int SizeImage; // 位图的大小,以字节为单位 (34-37 字节 ) int biXPelsPerMeter; // 位图水平分辨率,每米像素数 (38-41 字节 ) int biYPelsPerMeter; // 位图垂直分辨率,每米像素数 (42-45 字节 ) int biClrUsed;// 位图实际使用的颜色表中的颜色数 (46-49 字节 ) int biClrImportant;// 位图显示过程中重要的颜色数 (50-53 字节 ) |
③ 颜色表
颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个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]; // 颜色表 } |
④ 位图数据
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右, 扫描行之间是从下到上。位图的一个像素值所占的字节数:
当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图片的查看器 (暂时只做24位BMP图像解析器,大多数BMP图像也是2位):
package data0605_bmp; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.UIManager; import javax.swing.UIManager.LookAndFeelInfo; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.filechooser.FileNameExtensionFilter; /* * bmp文件读取保存 */ public class Projiect extends JFrame{ int map[][];//保存像素颜色的数组 MyPanel center;//绘图面板 File selectFile;//读取的文件 int width;//图像宽度 int height;//图像高度 byte temp1[];//读取BMP文件的前18个字节信息 byte temp2[];//读取BMP文件的后28个字节信息 JScrollPane scrollpane;//滚动面板 public Projiect() { //观感必须为main方法的第一个操作,否则可能不起作用 try { UIManager .setLookAndFeel(new com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel()); } catch (Exception e) { e.printStackTrace(); } this.setLayout(new BorderLayout());//最好先设置布局再添加组件 //初始化画图面板 center=new MyPanel(); center.setBackground(Color.WHITE); center.setPreferredSize(new Dimension(400,200)); scrollpane=new JScrollPane(center);//用center初始化滚动面板 scrollpane.setPreferredSize(new Dimension(500,300)); MyListener lis=new MyListener(); //初始化菜单栏 JMenuBar menuBar=new JMenuBar(); JMenu fileMenu=new JMenu("file"); JMenuItem open=new JMenuItem("open"); JMenuItem save=new JMenuItem("save"); open.addActionListener(lis); save.addActionListener(lis); fileMenu.add(open); fileMenu.add(save); menuBar.add(fileMenu); // JPanel left=new JPanel(); left.setBackground(Color.DARK_GRAY); left.setPreferredSize(new Dimension(50,0)); this.add(left,BorderLayout.WEST); this.setJMenuBar(menuBar); this.add(scrollpane,BorderLayout.CENTER);//加入的是滚动面板 this.setTitle("bmp"); this.setLocation(300, 150); this.setSize(800, 500); this.setDefaultCloseOperation(3); this.setVisible(true); } /** * 读取bmp文件 */ public void readBMP() { try { FileInputStream fis=new FileInputStream(selectFile); BufferedInputStream bis=new BufferedInputStream(fis); byte[] wb=new byte[4];//读取宽度的字节数组 byte[] hb=new byte[4];//读取高度的字节数组 temp1=new byte[18]; bis.read(temp1);//bis.skip(18);//跳过前18个byte bis.read(wb);//读取宽度 bis.read(hb);//读取高度 width=byteToint(wb); height=byteToint(hb); System.out.println(width+"<>"+height); map=new int[height][width]; int skip=4-width*3%4;//得到每行要跳过的数字(与windows 系统机制有关) temp2=new byte[28]; bis.read(temp2);//bis.skip(28); for(int i=0;i<height;i++) { for(int j=0;j<width;j++) { int blue=bis.read(); int green=bis.read(); int red=bis.read(); Color c=new Color(red,green,blue); map[i][j]=c.getRGB(); } if(skip!=4) bis.skip(skip); } bis.close(); center.setPreferredSize(new Dimension(width,height)); javax.swing.SwingUtilities.updateComponentTreeUI(center);//这里必须要用updateComponentTreeUI(center)函数 //不能用repaint()函数 } catch (Exception e) { e.printStackTrace(); } } public void writeBMP() { try { FileOutputStream fos=new FileOutputStream(selectFile); BufferedOutputStream bos=new BufferedOutputStream(fos); bos.write(temp1);// bos.write(intTobyte(width,4));//写入宽度 bos.write(intTobyte(height,4));//写入高度 bos.write(temp2); int skip=0; if(width*3/4!=0) { skip=4-width*3%4; } for(int i=0;i<height;i++) { for(int j=0;j<width;j++) { Color c=new Color(map[i][j]); int blue=c.getBlue(); int green=c.getGreen(); int red=c.getRed(); bos.write(blue); bos.write(green); bos.write(red); } if(skip!=0) bos.write(new byte[skip]); } bos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } } //字节转int public static int byteToint(byte b[]) { int t1=(b[3]&0xff)<<24; int t2=(b[2]&0xff)<<16; int t3=(b[1]&0xff)<<8; int t4=b[0]&0xff; //System.out.println(b[1]&0xff);//输出的是一个整形数据 //在java中,设计int和比int位数来的小的类型b,如byte,char等,都是先把小类型扩展成int再来运算, //return( t1<<24)+(t2<<16)+(t3<<8)+t4;//必须加括号 return t1+t2+t3+t4; } //int 转byte public static byte[] intTobyte(int a,int len) { byte []t=new byte[len]; t[0]=(byte) ((a&0xff)); if(len>1) t[1]=(byte)((a&0xff00)>>8); if(len>2) t[2]=(byte)((a&0xff0000)>>16); if(len>3) t[3]=(byte)((a&0xff000000)>>24); return t; } /** * 绘图面板 * @author ZhangZunC * */ class MyPanel extends JPanel{ public void paint(Graphics g) { super.paint(g); if(map!=null) { for(int i=0;i<map.length;i++) { for(int j=0;j<map[i].length;j++) { g.setColor(new Color(map[i][j])); g.drawLine(j, height-i, j,height- i); } } } } } class MyListener implements ActionListener{ JFileChooser fileChosser; MyListener() { FileNameExtensionFilter filter=new FileNameExtensionFilter("24位位图(*.bmp)", "bmp"); fileChosser=new JFileChooser(); fileChosser.setFileFilter(filter); fileChosser.setCurrentDirectory(new File("\\")); } public void actionPerformed(ActionEvent e) { if(e.getActionCommand().equals("open"))//选择的是打开 { int choose=fileChosser.showOpenDialog(null); if(choose==JFileChooser.APPROVE_OPTION)//点击的是确定按钮 { selectFile=fileChosser.getSelectedFile(); readBMP(); } } if(e.getActionCommand().equals("save"))//选择的是打开 { int choose=fileChosser.showSaveDialog(null); if(choose==JFileChooser.APPROVE_OPTION) { selectFile=fileChosser.getSelectedFile(); writeBMP(); System.out.println("save "); } } } } public static void main(String[] args) { new Projiect(); // byte[] b=new byte[4]; // b[0]=0; // b[1]=(byte) 0xaf; // b[2]=0; // b[3]=0; // System.out.println(byteToint(b)); // byte b1[]=intTobyte(byteToint(b),4); // System.out.println(b1[0]+"<>"+b1[1]+"<>"+b1[2]+"<>"+b1[3]); } }