JAVA如何自己写方法读取BMP文件


BMP图像解析

 

BMP是一种与硬件设备无关的图像文件格式,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此, BMP 文件所占用的空间很大。 BMP 文件的图像深度可选 lbit 、 4bit 、 8bit 及 24bit 。 BMP 文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。 由于 BMP 文件格式是 Windows 环境中交换与图有关的数据的一种标准,因此在 Windows 环境中运行的图形图像软件都支持 BMP 图像格式。 

 

要解析文件,就必须知道他的文件结构:

6.3.1 BMP文件结构 

典型的BMP 图像文件由四部分组成:   

 . 位图文件 头数据结构 ,它包含BMP 图像文件的类型、显示内容等信息;  

 .位图信息数据结构 ,它包含有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;// 保留,必须为    

}

颜色表中RGBQUAD 结构数据的个数有 biBitCount 来确定biBitCount=1,4,8 时,分别有 2,16,256 个表项 biBitCount=24 时,没有颜色表项。   

位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下    

class  BITMAPINFO {   

BITMAPINFOHEADER bmiHeader; // 位图信息头   

RGBQUAD bmiColors[1]; // 颜色表   

}  

 

④ 位图数据    

位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右扫描行之间是从下到上。位图的一个像素值所占的字节数   

biBitCount=1 时, 个像素占 个字节    

biBitCount=4 时, 个像素占 个字节    

biBitCount=8 时, 个像素占 个字节    

biBitCount=24 时 ,1 个像素占 个字节    

Windows规定一个扫描行所占的字节数必须是的倍数 即以 long 为单位 ), 不足的以 填充,具体数据举例:如某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位)


JAVA如何自己写方法读取BMP文件
 
JAVA如何自己写方法读取BMP文件
 

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]);
	}
}