欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

BMP文件格式解析

程序员文章站 2022-07-07 12:28:10
...

BMP文件格式解析

作者:水木子

一、图像概述

1.1 位图

位图Bitmap),又称栅格图(英语:Raster graphics)或点阵图,是使用像素阵列来表示的图像。 ----*

简单地说,位图就是由一个一个像素点构成的图像。常见的图像格式有jpg(jpeg)、png、bmp都是位图。

1.2 矢量图

矢量图形是计算机图形学中用点、直线或者多边形等基于数学方程的几何图元表示图像。

​ ----*

矢量图与位图不同,它不是由一个一个像素点构成的,它的实质是数学表达式。svg格式文件是矢量图。

1.3 位图与矢量图的区别

位图与矢量图最明显的区别是:位图放大会出现马赛克,画质变差;矢量图可以在不降低画质的条件下无限放大。

BMP文件格式解析

图像来源:*


图中a表示原图像。若a为矢量图,则放大红框中图像时,效果如b,可以看见图像质量并没有下降;如果a是位图,则放大红框中图像时,效果如c,可以明显看见有一个一个小方格,图像质量明显下降。

1.4 如何表示像素点的颜色

选择一张位图,在PS中放大到3200%,如下所见:

BMP文件格式解析

可以很明显得看见一个一个的小方块,这就是像素点。

一个像素点有特定的位置和颜色值。每个像素的颜色由RGB组合或灰度值表示。

本小节重点介绍如何表示颜色。

根据位深度,可以将位图分为1、4、8、16、24及32位图像等。这里的位深度是指用于表示像素点颜色的比特位个数。如果一个像素点由一个比特位表示颜色,那么其位深度为1,如果一个像素点由四个比特位表示颜色,则其位深度为4,依次类推。

  • 如果一张图片的像素点由1个比特位来表示颜色,则这个比特位为0或1,则可以表示21种颜色,即黑与白,则这张照片是纯粹的黑白照片。
  • 如果一张图片的像素点由8个比特位来表示颜色,则这八个比特位共可以表示28种颜色,即256。这种图像通常(有例外,下面会讲)称为灰度图,因为这256种颜色是黑白灰(这里的灰是指244种不同程度的灰)。如下为灰度图:

    BMP文件格式解析

  • 如果一张图片的像素点由24个比特位来表示颜色,则这24个比特位共可以表示224种颜色,即有1600万个以上的颜色。这种图像称为真彩色图。这24个比特位以8位分为三个通道,分别表示红、绿、蓝。这就是RGB颜色编码方式,用红、绿、蓝三原色的光学强度来表示一种颜色。这是最常见的位图编码方法,可以直接用于屏幕显示。

二、BMP文件格式

2.1 BMP简介

BMP取自位图Bitmap的缩写,也称为DIB(与设备无关的位图),是一种独立于显示器的位图数字图像文件格式。常见于微软视窗和OS/2操作系统。 ----*

BMP格式就是表示位图的格式。

BMP格式图像中的像素点,其位深度可以是1,4,8,24,32,但常见的BMP位深度还是8和24。

选择一张BMP图像,右键打开属性 --> 详细信息,可以查看其位深度。

当BMP文件位深度为8时,并不一定代表这张图片为灰度图,如下:

BMP文件格式解析

这张图片的位深度为8,但其并不是灰度图,我们称之为伪彩色图

下面这张图是位深度为24的真彩色图,可以作为对比:

BMP文件格式解析

可以看出真彩色图的质量明显高于伪彩色图。

2.2 BMP文件格式组成

BMP文件由以下四部分组成:

  • 位图文件头(BITMAPFILEHEADER)
  • 位图信息头(BITMAPINFOHEADER)
  • 颜色表*(RGBQUAD[])
  • 像素阵列(Pixels[][])

由于颜色表并不一定存在,故加*说明。

下面先将各部分的信息简单说明一下:

2.2.1 位图文件头

用于描述整个bmp文件的情况,具体包括BMP文件的类型、文件大小和位图起始位置等信息。

typedef struct tagBITMAPFILEHEADER{
    WORD	bfType;                  // 位图文件的类型,必须为BMP (2个字节)
    DWORD	bfSize;                  // 位图文件的大小,以字节为单位 (4个字节)
    WORD	bfReserved1;             // 位图文件保留字,必须为0 (2个字节)
    WORD	bfReserved2;             // 位图文件保留字,必须为0 (2个字节)
    DWORD	bfOffBits;               // 位图数据的起始位置,以相对于位图 (4个字节)
} BITMAPFILEHEADER;

位图文件头总共有14个字节

2.2.2 位图信息头

用于描述位图的尺寸等信息。

typedef struct tagBITMAPINFOHEADER{
	DWORD biSize;     		// 本结构所占用字节数  (4个字节)
	LONG biWidth;      		// 位图的宽度,以像素为单位(4个字节)
	LONG biHeight;     		// 位图的高度,以像素为单位(4个字节)
	WORD biPlanes;    		// 目标设备的级别,必须为1(2个字节)
	WORD biBitCount; 		// 每个像素所需的位数,必须是1(双色)、// 4(16色)、8(256色)、
	    					//24(真彩色)或32(增强真彩色)之一 (2个字节)
	DWORD biCompression; 	// 位图压缩类型,必须是 0(不压缩)、 1(BI_RLE8 
	                     	// 压缩类型)或2(BI_RLE4压缩类型)之一 ) (4个字节)
	DWORD biSizeImage;     	// 位图的大小,以字节为单位(4个字节)
	LONG biXPelsPerMeter;  	// 位图水平分辨率,每米像素数(4个字节)
	LONG biYPelsPerMeter;   // 位图垂直分辨率,每米像素数(4个字节)
	DWORD biClrUsed;        // 位图实际使用的颜色表中的颜色数(4个字节)
	DWORD biClrImportant;   // 位图显示过程中重要的颜色数(4个字节)
} BITMAPINFOHEADER;

位图信息头一共有40个字节

2.2.3 颜色表

用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。

typedef struct tagRGBQUAD 
{
  BYTE rgbBlue;          // 蓝色的亮度(值范围为0-255)
  BYTE rgbGreen;         // 绿色的亮度(值范围为0-255)
  BYTE rgbRed;           // 红色的亮度(值范围为0-255)
  BYTE rgbReserved;      // 保留,必须为0
} RGBQUAD;

可以看到一个RGB表项为4个字节

颜色表中RGBQUAD结构数据的个数由位图信息头中的biBitCount来确定:

  • 当biBitCount=1, 4, 8时,分别有2, 16,256个表项
  • 当biBitCount=24时,没有颜色表项

2.2.4 像素阵列

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

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

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

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

  • 当biBitCount=24时,1个像素占3个字节,分别是R、G、B;

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

三、实例解析BMP文件格式

用notepad++打开grey8.bmp文件,选择插件 --> HEX-Editor --> View in HEX,如果没有可自行选择插件管理进行安装,最终界面如下:

BMP文件格式解析

这是以十六进制的形式显示图像信息,一个十六进制数字占4个比特位,故一行表示十六个字节。

在对BMP文件进行具体解析前,我们还要先了解一下数据的存放顺序:

在BMP文件中,如果一个数据需要用几个字节来表示的话,那么该数据的存放字节顺序为“低地址存放低位数据,高地址存放高位数据”。如数据0x1756在内存中的存储顺序为:

BMP文件格式解析

这种存储方式称为小端方式(little endian) , 与之相反的是大端方式(big endian)。

3.1 位图文件头

下图红框中为位图文件头:

BMP文件格式解析

  • 头两个字节(0,1)表示位图文件的类型,即0x4d42表示类型,为BMP,这与DUMP中的头两个字节表示的符号BM一致

  • 后面的四个字节(2,3,4,5)表示位图文件的大小,即0x0000c436表示位图文件大小,换算为十进制为50230,我们打开grey8.bmp的属性,发现其大小确为50230字节:

    BMP文件格式解析

    位图文件的大小,即bfSize的值需要如何计算呢?
    bfSize=14+40+4+/8 bfSize=位图文件头的大小(14字节)+位图信息头的大小(40字节)\\+颜色表项*4字节+图像的宽度*高度*位深度/8(字节)
    我们打开grey8.bmp的属性,点击详细信息,可以看到grey8.bmp的大小为256x192像素,位深度为8,又因为其位深度为8,故其颜色表项有256项(每项占据4字节),故grey8.bmp的大小为:
    bfSize=14+40+2564+2561928/8=50230() bfSize=14+40+256*4+256*192*8/8=50230(字节)

  • 再后面的两个字节(6,7)是位图文件保留字1,必须为0,即为0x0000。

  • 再后面的两个字节(8,9)是位图文件保留字2,必须为0,即为0x0000。

  • 最后的四个字节(a,b,c,d)是位图数据的起始位置,其值为0x00000436,换算为十进制为1078。这个表示的是从文件开始到像素阵列之间的字节数,即其大小为:位图文件头(14字节)+位图信息头(40字节)+[256个表项*4字节],因为颜色表项并不一定存在,故用[]括起来。在grey8.bmp图像中存在颜色表,故位图数据的起始值为1078。

3.2 位图信息头

BMP文件格式解析

  • 头四个字节(第一行的e,f,第二行的0,1)表示位图信息头所占用的字节数,即为0x00000028,换算为十进制是40;

  • 后四个字节(第二行的2,3,4,5)为位图的以像素为单位的宽度,即0x00000100,换算为十进制为256,与实际相符。

  • 后四个字节(第二行的6,7,8,9)为位图的以像素为单位的高度,即0x000000c0,换算为十进制为192,与实际相符。

  • 后两个字节(第二行的a,b)为目标设备的级别,必须为1,其值为0x0001,相符。

  • 后两个字节(第二行的c,d)为每个像素所需的位数,其值为0x0008,与实际的位深度相一致。

  • 后四个字节(第二行的e,f,第三行的0,1)表示位图的压缩类型,其值为0x00000000,即不压缩。

  • 后四个字节(第三行的2,3,4,5)为位图的大小,以字节为单位,其值为0x0000c000,换算为十进制为49152,其实就是计算像素阵列的大小,计算方式为:
    biSizeImage=/8 biSizeImage=图像的宽度*高度*位深度/8
    在grey8.bmp图像中
    biSizeImage=2561928/8=49152 biSizeImage=256*192*8/8=49152

  • 后四个字节(第三行的6,7,8,9)表示位图的水平分辨率,其值为0x00002e23。

  • 后四个字节(第三行的a,b,c,d)表示位图的垂直分辨率,其值为0x00002e23。

  • 后四个字节(第三行的e,f,第四行的0,1)表示位图实际使用的颜色表中的颜色数,其值为0x00000000,一般置为0。

  • 最后的四个字节(第四行的2,3,4,5)表示位图显示过程中重要的颜色数,其值为0x00000000,一般置为0。

3.3 颜色表

当位深度为24时,没有颜色表,位图信息头紧接着就是像素阵列;

当位深度不为24时,有颜色表,并且有2位深度个颜色表项,每个表项占据4个字节。

在如下图的黄框中,共有256个颜色表项,共由256*4个字节(只显示了一部分):

BMP文件格式解析

由于每个颜色表项为4个字节,因此我们以4个字节为单位划分为一个个的颜色表项,即一个黑框。

由于grey8.bmp是灰度图,因此其颜色表项是由规律可循的。

在颜色表项内部,RGB三个分量相等,第4个分量为0;在整个颜色表中,颜色表项的前三个分量数值由0到255依次递增1。其实,rgb(0,0,0)代表黑色,rgb(255,255,255)代表白色,rgb(x,x,x)(x不等于0或255,x为0到255之间的整数)代表不同程度的灰色。

当BMP文件为伪彩色图时,其位深度是8位,但并没有规律可循。比如第一个颜色表项是rgb(1,22,3,0),第二个颜色表项是rgb(10,89,90,0),依次类推。这些颜色并不是黑白灰,而是会显示出其他颜色,但是,最多只能显示256种颜色,远远不如真彩色图的1600万多种颜色,因此这类图像叫做伪彩色图。

3.4 像素阵列

颜色表(或位图信息头)后就是像素阵列,在本例中,位深度为8,故一个字节就代表一个像素点。那如何确定这个像素点的颜色呢?一个字节表示的范围为[0,255],这下你该明白了吧!根据这个字节的值找到相应的颜色表项,这个颜色表项所对应的颜色就是这个像素的颜色。对于有颜色表的BMP,其运作方式就是如此。

对于真彩色图,其位深度为24,一个像素就自然而然地对应着R、G、B三个颜色分量,故不需要颜色表了,并且,对于真彩色图,如果有颜色表,那么其有1600万多个颜色表项,整个文件将会非常庞大。

三、代码实验

3.1 实验环境

  • 操作系统:Windows 10
  • 编译器:Dev-cpp,Visual Studio 2017

3.2 实验内容

将以下grey8_test.bmp灰度图的颜色表项修改为随机值,将原灰度图变为伪彩色图。

BMP文件格式解析

3.3 其他信息

BITMAPFILEHEADER、BITMAPINFOHEADER、RGBQUAD 这三个结构体在windows.h中都有定义

3.4 关键代码

因为代码都有详细注释,故在此处不再详细说明。

  • 读取相关信息:
	BITMAPFILEHEADER *fileHeader;
	BITMAPINFOHEADER *infoHeader;
	//为位图文件头、位图信息头指针分配空间 
	fileHeader = (BITMAPFILEHEADER *)malloc(sizeof(BITMAPFILEHEADER));
	infoHeader = (BITMAPINFOHEADER *)malloc(sizeof(BITMAPINFOHEADER));
	//读取灰度图src的位图文件头、位图信息头信息 
	fread(fileHeader, sizeof(BITMAPFILEHEADER), 1, src);
	fread(infoHeader, sizeof(BITMAPINFOHEADER), 1, src);
	//为颜色表指针分配空间 
	RGBQUAD *rgb = (RGBQUAD *)malloc(sizeof(RGBQUAD) * 256);
	//读取颜色表项
	fread(rgb, sizeof(RGBQUAD), 256, src);
  • 利用随机数函数修改颜色表项
	//修改颜色表项的RGB三分量的值 
	srand((unsigned)time(NULL));
	for (int i = 0; i < 256; i++) {
		rgb[i].rgbBlue = rand() % 255;
		rgb[i].rgbGreen = rand() % 255;
		rgb[i].rgbRed = rand() % 255;
	}
  • 将读取到的信息写入目标文件
	//将位图文件头、位图信息头、颜色表写入目标文件
	fwrite(fileHeader, sizeof(BITMAPFILEHEADER), 1, target);
	fwrite(infoHeader, sizeof(BITMAPINFOHEADER), 1, target);
	fwrite(rgb, sizeof(RGBQUAD), 256, target);
  • 读取像素信息并写入,注意四字节对齐
	//读取灰度图像的像素
	unsigned char *ch;
	ch = (unsigned char*)malloc(sizeof(unsigned char));

	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < ((width + 3) / 4) * 4; j++)
		{
			fread(ch, sizeof(unsigned char), 1, src);
			fwrite(ch, sizeof(unsigned char), 1, target);
		}
	}

3.5 注意点!!!

  • 以二进制形式打开文件!!!!!!!
  • 注意一行的像素所占的字节数是4的倍数。读文件时,要多读取后面补充的0字节;写文件时,也要多写需要填充的0字节。否则可能会出现图像错乱的现象。

3.6 结果

利用我们的程序,生成了以下图片,还挺漂亮的!

BMP文件格式解析

BMP文件格式解析

3.7 完整代码

参考code文件中的源.cpp和question3文件

四、扩展实验

  • 编写C/C++程序,读取一个24位真彩色BMP文件,然后转化为灰色图像,最后存储为8位伪彩色BMP文件。
  • 编写C/C++程序,读取一个8位伪彩色BMP文件,转化为24位真彩色BMP文件,最后存储。

备注:从RGB到灰度图的转换公式:Gray = R*0.299 + G*0.587 + B*0.114

五、参考资料

[1] https://www.cnblogs.com/Matrix_Yao/archive/2009/12/02/1615295.html

[2] 多媒体基础课程相关资料

[3] *关于“位图”、“矢量图”、“BMP格式”的介绍

相关标签: C/C++ bmp