BMP图片格式详细解析--以256色为例

通过分析bmp图片的格式,可以完成BMP图片的打开和保存

 

一、bmp格式:

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

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

 2 .位图信息数据结构,它包含有BMP 图像的宽、高、压缩方法,以及定义颜色等信息;  

      3.调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24 位的 BMP )就不需要调色板; 

4.位图数据,这部分的内容根据BMP 位图使用的位数不同而不同,在 24 位图中直接使用 RGB ,而其他的小于 24 位的使用调色板中颜色索引值。

 

BMP文件头 (14 字节 )   

 

BMP文件头数据结构含有 BMP 文件的类型、文件大小和位图起始位置等信息其结构定义如下:   

 

intbfType; // 位图文件的类型,必须为 ' B '' M '两个字母 (0-1字节 )  

intbfSize; // 位图文件的大小,以字节为单位 (2-5 字节 )  

intbfReserved1; // 位图文件保留字,必须为 0(6-7 字节 )   

intbfReserved2; // 位图文件保留字,必须为 0(8-9 字节 )  

intbfOffBits; // 位图数据的起始位置,以相对于位图 (10-13 字节 )   

 

 

 

位图信息头(40 字节 )  

 

 BMP 位图信息头数据用于说明位图的尺寸等信息。 

 

intSize; // 本结构所占用字节数 (14-17 字节 )  

intimage_width; // 位图的宽度,以像素为单位 (18-21 字节 )   

intimage_heigh; // 位图的高度,以像素为单位 (22-25 字节 )  

intPlanes; // 目标设备的级别,必须为 1(26-27 字节 )   

intbiBitCount;// 每个像素所需的位数,必须是或1,4,8 24(// 真彩色 ) 之一  (28-29 字节)

intbiCompression; // 位图压缩类型,必须是 0( 不压缩 ), 1(BI_RLE8 压缩类型 ) // 2(BI_RLE4 压缩类型 ) 之一  (30-33 字节 )

intSizeImage; // 位图数据的大小,以字节为单位 (34-37 字节 )   

intbiXPelsPerMeter; // 位图水平分辨率,每米像素数 (38-41 字节 )   

intbiYPelsPerMeter; // 位图垂直分辨率,每米像素数 (42-45 字节 )   

intbiClrUsed;// 位图实际使用的颜色表中的颜色数 (46-49 字节 )   

intbiClrImportant;// 位图显示过程中重要的颜色数 (50-53 字节 )

 

 

 

 

 

颜色表  

 

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

 

在windows中每个颜色是 b g r a 四个字节保存,a代表透明度,如果是1位位图,那么颜色表一共站八个字节,如果是4位位图,颜色表站84个字节,如果是8位位图,需要表示256中颜色,每种颜色站四个字节,所以颜色表一共站1024个字节。

 

256色的时候,windows中,位图数据的大小就是0-255,代表着调色板中的数据,每次读八个为就是一个像素信息,其他位图类似。

 

位图数据  

 

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

 

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

 

biBitCount=4 时, 2 个像素占 1 个字节 ;   

 

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

 

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

 

Windows规定一个扫描行所占的字节数必须是4 的倍数 ( 即以 long 为单位 ), 不足的以 0 填充,具体数据举例:

 

也就是说,写入图片一行的像素信息的时候,每一行的字节数都必须是4的倍数,不足的在后面补0,然后才又开始写入下一行的像素信息。 

 

位图数据,其实就是在描述图片上每一个点的颜色,在windos中,是先写入该图片的最有一排像素点,从下往上,从左往右,没写完一排,如果写入的字节数不是4的倍数,就会补0,比如写入了3个字节,就不要再补一个字节的0,然后开始写下一排的数据。

 

对于单色位图,只需要一个位就可以表示其所有的颜色可能,黑白,对于16色位图,也就是有16中颜色的可能,那么需要四个位就可以表示16中可能,对于256色位图,那么需要8个位才能表示256中情况,对于真彩色,也就是表示所有的颜色,那么需要三个字节才可以表示所有情况(透明度不算),对于单色,16色,256色位图来说,位图数据读到的是调色板上的索引,通过这个索引在调试版中找到对应的颜色,然后显示在屏幕上。所以对于单色位图,他的调色板只有黑白两种颜色,16色位图,他的调色板有16种颜色,256色的调色板有256中颜色,如果是24位位图,他的位图信息,就是保存的真实颜色,每读三个字节就表示了一个像素点的颜色,不需要再进行调色板查找对应的颜色。

 

 

 

前言:windows中存入基本数据,是先存入了低位,再存入高位。

 

******************************************************************************************

 

首先完成打开一张bmp24位位图图片:

 

1、利用系统画图板画一张图片,保存的时候选择24位位图,由于24为位图没有调试板,位图数据区保存的是每个像素点的颜色,不需要我们进行调试板的转换,所有有利于初学者对于bmp格式的理解,完成了24位的再来看其他位图的。

 

2、利用swing写一个图形界面,中间留一个panel区域用于显示图片。将图片的每一个颜色保存到二维数组中,然后画在面板上,就打开了一张图片。

 

3、读取到图片的主要信息:

 

Java代码 复制代码 收藏代码
  1. File file = new File("d:\\8bit.bmp");
  2. FileInputStream fis = new FileInputStream(file);
  3. // 首先得到宽度和高度
  4. fis.skip(18);
  5. int width = myReadInt(fis);
  6. int height = myReadInt(fis);
  7. //由于24位位图没有调试板数据,所以现在已经已经读取了26个字节,再跳过28个字节就到了位图
  8. //数据区
  9. fis.skip(28);
  10. //再开始读取就是位图数据了,现在是24位位图,每读取三个字节就代表一个像素点的颜色,注意
  11. //windows在保存的时候是先保存的最后一排的数据,从下往上从左往右保存的,所以我们取出数
  12. //据只有需要保存的数组正确的位置。每次读取三个字节我们就转成颜色保存起来
  13. // 现在读取位图数据
  14. // 判断是否补零
  15. Color[][] colors = new Color[height][width];
  16. int sk = 0;
  17. if (width % 4 > 0) {
  18. sk = 4 - width % 4;
  19. }
  20. for (int h = image_heigh - 1; h >= 0; h--) {
  21. for (int w = 0; w < image_width; w++) {
  22. // dis.skipBytes(bfOffBits-60);
  23. // 读入三原色
  24. int blue = dis.read();
  25. int green = dis.read();
  26. int red = dis.read();
  27. Color color = new Color(red,green,blue);
  28. colors[h][w]=color;
  29. }
  30. // 跳过补0项,每次读取第一行就判断需要跳过的字节
  31. dis.skipBytes(sk);
  32. }
File file = new File("d:\\8bit.bmp");
FileInputStream fis = new FileInputStream(file);
// 首先得到宽度和高度
fis.skip(18);
int width = myReadInt(fis);
int height = myReadInt(fis);
//由于24位位图没有调试板数据,所以现在已经已经读取了26个字节,再跳过28个字节就到了位图
//数据区
fis.skip(28);
//再开始读取就是位图数据了,现在是24位位图,每读取三个字节就代表一个像素点的颜色,注意
//windows在保存的时候是先保存的最后一排的数据,从下往上从左往右保存的,所以我们取出数
//据只有需要保存的数组正确的位置。每次读取三个字节我们就转成颜色保存起来
// 现在读取位图数据
// 判断是否补零
Color[][] colors = new Color[height][width];
int sk = 0;
if (width % 4 > 0) {
	sk = 4 - width % 4;
	}
		for (int h = image_heigh - 1; h >= 0; h--) {
					for (int w = 0; w < image_width; w++) {
						// dis.skipBytes(bfOffBits-60);
						// 读入三原色
						int blue = dis.read();
						int green = dis.read();
						int red = dis.read();
						Color color = new Color(red,green,blue);
						colors[h][w]=color;
					}
					// 跳过补0项,每次读取第一行就判断需要跳过的字节
					dis.skipBytes(sk);

				}

 

保存下来所有像素的颜色,就可以画在面板上了,面板的大小应该是图片的大小。

 

如果理解了文件格式含义,那么就可以尝试完成256色位图的打开和保存。

 

保存的时候,我们需要严格按照格式规定,写入正确的字节,如果是256色,需要进行颜色的转换:

 

下面是256色位图的打开:

 

说明:我先根据系统画图板的图片,保存一张256色图片,读取出了调调色板的数据,放在一个文件中

 

便于我在进行打开256色图片的时候,把位图数据读到的信息转成调色板对应的颜色,所以要完成下面的操作,请先自己

 

读取256色的调色板数据。把由于系统画图板,位图信息是0,代表的是第一个颜色,如果是255代表最后一个颜色,所以

 

我们可以把调色板数据放在一维数组中,利用下标来进行查找。

 

 

Java代码 复制代码 收藏代码
  1. // 打开256色,也就是8位表示一个像素点的颜色
  2. File file = new File("d:\\8bit.bmp");
  3. FileInputStream fis = new FileInputStream(file);
  4. // 首先得到宽度和高度
  5. fis.skip(18);
  6. int width = myReadInt(fis);
  7. int height = myReadInt(fis);
  8. // 现在流的位置,26
  9. // 再跳过12个字节,到达需要测试的数据区
  10. fis.skip(12);
  11. int biXPelsPerMeter = myReadInt(fis); // 位图水平分辨率,每米像素数 (38-41 字节
  12. // )   
  13. int biYPelsPerMeter = myReadInt(fis); // 位图垂直分辨率,每米像素数 (42-45 字节
  14. // )   
  15. int biClrUsed = myReadInt(fis);// 位图实际使用的颜色表中的颜色数 (46-49 字节 )   
  16. int biClrImportant = myReadInt(fis);// 位图显示过程中重要的颜色数 (50-53 字节 )
  17. System.out.println("" + biXPelsPerMeter + " " + biYPelsPerMeter
  18. + " " + biClrUsed + " " + biClrImportant);
  19. // 由于是256色,所以颜色表是[256][4]大小的数组,每个分量占1个字节
  20. // 定义一个256的数组,保存下颜色表的数据
  21. Color[] colors256 = new Color[256];
  22. for (int i = 0; i < 256; i++) {
  23. colors256[i] = myReadColor(fis);
  24. System.out.println("颜色表" + colors256[i]);
  25. }
  26. // 现在读取位图数据
  27. // 判断是否补零
  28. int sk = 0;
  29. if (width % 4 > 0) {
  30. sk = 4 - width % 4;
  31. }
  32. DrawUI.array = newint[height][width];
  33. System.out.println("宽度" + width + "高度" + height);
  34. for (int i = height - 1; i >= 0; i--) {
  35. for (int j = 0; j < width; j++) {
  36. int index = fis.read();
  37. DrawUI.array[i][j] = colors256[index].getRGB();
  38. System.out.println("保存的值"+DrawUI.array[i][j]);
  39. }
  40. fis.skip(sk);
  41. }
  42. // 设置画布大小
  43. panel.setPreferredSize(new Dimension(width, height));
  44. // 重新绘制panel,如果是repaint,只会重新绘制原来panel中的内容,不会将panel更新大小
  45. SwingUtilities.updateComponentTreeUI(panel);
// 打开256色,也就是8位表示一个像素点的颜色
				File file = new File("d:\\8bit.bmp");
				FileInputStream fis = new FileInputStream(file);
				// 首先得到宽度和高度
				fis.skip(18);
				int width = myReadInt(fis);
				int height = myReadInt(fis);
				// 现在流的位置,26

				// 再跳过12个字节,到达需要测试的数据区
				fis.skip(12);
				int biXPelsPerMeter = myReadInt(fis); // 位图水平分辨率,每米像素数 (38-41 字节
														// )   
				int biYPelsPerMeter = myReadInt(fis); // 位图垂直分辨率,每米像素数 (42-45 字节
														// )   
				int biClrUsed = myReadInt(fis);// 位图实际使用的颜色表中的颜色数 (46-49 字节 )   
				int biClrImportant = myReadInt(fis);// 位图显示过程中重要的颜色数 (50-53 字节 )
				System.out.println("" + biXPelsPerMeter + " " + biYPelsPerMeter
						+ " " + biClrUsed + " " + biClrImportant);

				// 由于是256色,所以颜色表是[256][4]大小的数组,每个分量占1个字节
				// 定义一个256的数组,保存下颜色表的数据
				Color[] colors256 = new Color[256];
				

				for (int i = 0; i < 256; i++) {
					colors256[i] = myReadColor(fis);
					System.out.println("颜色表" + colors256[i]);
				}
				// 现在读取位图数据
				// 判断是否补零

				int sk = 0;
				if (width % 4 > 0) {
					sk = 4 - width % 4;
				}

				DrawUI.array = new int[height][width];
				System.out.println("宽度" + width + "高度" + height);
				for (int i = height - 1; i >= 0; i--) {
					for (int j = 0; j < width; j++) {
						int index = fis.read();
						DrawUI.array[i][j] = colors256[index].getRGB();
						System.out.println("保存的值"+DrawUI.array[i][j]);

					}
					fis.skip(sk);

				}
				// 设置画布大小
				panel.setPreferredSize(new Dimension(width, height));
				// 重新绘制panel,如果是repaint,只会重新绘制原来panel中的内容,不会将panel更新大小
				SwingUtilities.updateComponentTreeUI(panel);

 

 

下面的代码,保存256色位图的bmp图片,注意需要自己写入调色板,调色板的信息是按照b g r a的顺序写入,调色板字节

 

先要准备好。

Java代码 复制代码 收藏代码
  1. // 将图片保存为bmp格式
  2. File mysave256 = new File("d:\\mysave256bmp.bmp");
  3. FileOutputStream fos = new FileOutputStream(mysave256);
  4. // 先写bm
  5. fos.write('B');
  6. fos.write('M');
  7. int width = DrawUI.array[0].length;
  8. int height = DrawUI.array.length;
  9. int bu0 = 0;
  10. if (width * bit % 4 > 0) {
  11. bu0 = 4 - width * bit % 4;
  12. }
  13. int size = 14 + 40 + width * height * bit + bu0 * height;
  14. // 写入文图文件大小
  15. myWriteInt(fos, size);
  16. // 写入文件保留字
  17. myWriteShort(fos, (short) 0);
  18. myWriteShort(fos, (short) 0);
  19. // 位图数据起始位置,54 + 颜色表
  20. int beginweitu = 54 + 256 * 4;
  21. myWriteInt(fos, beginweitu);
  22. // 位图信息头
  23. // 写入写入信息大小
  24. myWriteInt(fos, 40);
  25. // 写入宽度和高度
  26. myWriteInt(fos, width);
  27. myWriteInt(fos, height);
  28. // 写入级别
  29. myWriteShort(fos, (short) 1);
  30. // 每个像素所需的位数
  31. myWriteShort(fos, (short) 8);
  32. // 位图压缩类型
  33. myWriteInt(fos, 0);
  34. // 位图数据的大小
  35. int wsize = width * height + bu0 * height;
  36. myWriteInt(fos, wsize);
  37. // 保留的字节
  38. myWriteInt(fos, 0);
  39. myWriteInt(fos, 0);
  40. myWriteInt(fos, 0);
  41. myWriteInt(fos, 0);
  42. // 开始写入调色板
  43. FileInputStream fiscolor = new FileInputStream(fcolor256);
  44. // 得到调试板数组,用于转换
  45. HashMap hm = new HashMap();
  46. for (int i = 0; i < 256; i++) {
  47. int b = fiscolor.read() & 0xff;
  48. int g = fiscolor.read() & 0xff;
  49. int r = fiscolor.read() & 0xff;
  50. int a = fiscolor.read() & 0xff;
  51. Color colora =new Color(r,g,b) ;
  52. hm.put(colora.getRGB(),i );
  53. fos.write(b);
  54. fos.write(g);
  55. fos.write(r);
  56. fos.write(a);
  57. }
  58. fiscolor.close();
  59. // 开始写入位图数据,从最后一行从下往上输入
  60. int b0 = 0;
  61. if (width % 4 > 0) {
  62. b0 = 4 - width % 4;
  63. }
  64. int yuan = 0;
  65. int zhuan = 0;
  66. for (int i = height - 1; i >= 0; i--) {
  67. for (int j = 0; j < width; j++) {
  68. if(DrawUI.array[i][j]!=-1){
  69. System.out.print(DrawUI.array[i][j]+" ");
  70. }
  71. yuan = DrawUI.array[i][j];
  72. try{
  73. zhuan =(Integer) hm.get(yuan);
  74. }catch (Exception e2){
  75. zhuan=0;
  76. }
  77. fos.write(zhuan);
  78. }
  79. System.out.println();
  80. for (int k = 0; k < b0; k++) {
  81. fos.write(0);
  82. }
  83. }
  84. fos.flush();
  85. fos.close();
  86. publicvoid myWriteInt(OutputStream os, int num) throws IOException {
  87. int a = num & 0xff;
  88. int b = (num >> 8) & 0xff;
  89. int c = (num >> 16) & 0xff;
  90. int d = (num >> 24) & 0xff;
  91. os.write(a);
  92. os.write(b);
  93. os.write(c);
  94. os.write(d);
  95. os.flush();
  96. }
  97. publicvoid myWriteShort(OutputStream os, short num) throws IOException {
  98. int a = num & 0xff;
  99. int b = (num >> 8) & 0xff;
  100. os.write(a);
  101. os.write(b);
  102. os.flush();
  103. }

你可能感兴趣的:(IO流,bmp,图片格式)