BMP图像解析
平常我们接触到各种图像格式,但是这些图像格式具体是在计算机中运行的,今天我们来简单探讨一下BMP文件格式,BMP是一种与硬件设备无关的图像文件格式,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此, BMP 文件所占用的空间很大。 BMP 文件的图像深度可选 lbit 、 4bit 、 8bit 及 24bit 。 BMP 文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。 由于 BMP 文件格式是 Windows 环境中交换与图有关的数据的一种标准,因此在 Windows 环境中运行的图形图像软件都支持 BMP 图像格式。
要解析文件,就必须知道他的文件结构:
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 个字节 ;
import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.io.BufferedInputStream; import java.io.FileInputStream; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; /** * 图片查看器窗口 * * @author 王伟 * */ public class PicUI extends JFrame { public static void main(String args[]) { PicUI ui = new PicUI(); ui.initUI(); } public void initUI() { this.setTitle("图片查看器"); this.setSize(600, 500); // 设置布局为流式布局 FlowLayout layout = new FlowLayout(); this.setLayout(layout); // 用来显示图片的JPanel JPanel center = new MyPanel(); center.setPreferredSize(new Dimension(400, 300)); center.setBackground(Color.WHITE); this.add(center); this.setDefaultCloseOperation(3); this.setVisible(true); } /** * 读取BMP文件的方法(暂时考虑24位位图) * * @param path */ public int[][] readFile(String path) { try { // 根据文件路径创建输入流对象 FileInputStream fis = new FileInputStream(path); // 包装成缓冲流,提高效率 BufferedInputStream bis = new BufferedInputStream(fis); // 读取BMP文件流中的18~21个字节,图片的宽度 // 丢弃掉前18个字节 bis.skip(18); byte[] b = new byte[4]; bis.read(b); // 读取BMP文件流中的22~25个字节,图片的高度 byte[] b2 = new byte[4]; bis.read(b2); // 得到宽度和高度 int width = byte2Int(b); int height = byte2Int(b2); System.out.println(width + "<>" + height); // 定义二位数组来存储读取到的位图数据 int[][] data = new int[height][width]; // // 定义三个数组用来存储RGB三种颜色 // int[][] R = new int[height][width]; // int[][] G = new int[height][width]; // int[][] B = new int[height][width]; int skipNum = 0;// 每行补0的个数 if (width * 3 / 4 != 0) { // 计算补0的个数 skipNum = 4 - width * 3 % 4; } // 读位图数据 bis.skip(28);// 丢弃28个字节,来到位置数据处 for (int i = 0; i < data.length; i++) { for (int j = 0; j < data[i].length; j++) { // // 每次读取3个字节,表示的是三种颜色 // B[i][j] = bis.read(); // G[i][j] = bis.read(); // R[i][j] = bis.read(); int blue = bis.read(); int green = bis.read(); int red = bis.read(); // 将三种颜色构造成一种颜色 Color c = new Color(red, green, blue); data[i][j] = c.getRGB(); } // 如果补0的个数不为0,则需要跳过这些补上的0 if (skipNum != 0) { bis.skip(skipNum); } } return data; } catch (Exception ef) { ef.printStackTrace(); } return null; } // 将4个byte拼成一个int public int byte2Int(byte[] by) { int t1 = by[3] & 0xff; int t2 = by[2] & 0xff; int t3 = by[1] & 0xff; int t4 = by[0] & 0xff; int num = t1 << 24 | t2 << 16 | t3 << 8 | t4; return num; } class MyPanel extends JPanel { public void paint(Graphics g) { super.paint(g); // 读取数据 int[][] data = readFile("F:\\image\\abc.bmp"); if (data != null) { this.setPreferredSize(new Dimension(data[0].length, data.length)); // SwingUtilities.updateComponentTreeUI(this); for (int i = 0; i < data.length; i++) { for (int j = 0; j < data[i].length; j++) { Color c = new Color(data[i][j]);// 得到一个点的颜色 g.setColor(c); // 绘制这个点 g.drawLine(j, data.length - i, j, data.length - i); } } } System.out.println("wanbi"); } } }