BMP文件格式解析

  • Post author:
  • Post category:其他



BMP文件格式解析

作者:水木子



一、图像概述



1.1 位图


位图



Bitmap

),又称

栅格图

(英语:

Raster graphics

)或

点阵图

,是使用像素阵列来表示的图像。 —-维基百科

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



1.2 矢量图

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

​ —-维基百科

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



1.3 位图与矢量图的区别

位图与矢量图最明显的区别是:

位图放大会出现马赛克,画质变差;矢量图可以在不降低画质的条件下无限放大。

在这里插入图片描述


图像来源:维基百科

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



1.4 如何表示像素点的颜色

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MESatFy9-1573204554252)(img/image-20191102143341677.png)]

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

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

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

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

  • 如果一张图片的像素点由

    1个比特位

    来表示颜色,则这个比特位为0或1,则可以表示2

    1

    种颜色,即黑与白,则这张照片是纯粹的黑白照片。
  • 如果一张图片的像素点由

    8个比特位

    来表示颜色,则这八个比特位共可以表示2

    8

    种颜色,即256。这种图像

    通常

    (有例外,下面会讲)称为

    灰度图

    ,因为这256种颜色是黑白灰(这里的灰是指244种不同程度的灰)。如下为灰度图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XND0Cdem-1573204554252)(img/grey8.bmp)]

  • 如果一张图片的像素点由

    24个比特位

    来表示颜色,则这24个比特位共可以表示2

    24

    种颜色,即有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时,并不一定代表这张图片为灰度图,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mo8accMD-1573204554254)(img/swift8.bmp)]

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

伪彩色图

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wwYgzFde-1573204554258)(img/swift24.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,如果没有可自行选择插件管理进行安装,最终界面如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MYsNmSvS-1573204554260)(img/image-20191102154654330.png)]

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NSD9dSVY-1573204554262)(img/bmp_5.png)]

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



3.1 位图文件头

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

  • 头两个字节(0,1)表示位图文件的类型,即

    0x4d42

    表示类型,为BMP,这与

    DUMP中的头两个字节表示的符号BM一致

  • 后面的四个字节(2,3,4,5)表示位图文件的大小,即

    0x0000c436

    表示位图文件大小,换算为十进制为50230,我们打开grey8.bmp的属性,发现其大小确为50230字节:

    位图文件的大小,即bfSize的值需要如何计算呢?





    b

    f

    S

    i

    z

    e

    =

    14

    +

    40

    +

    4

    +

    /

    8

    bfSize=位图文件头的大小(14字节)+位图信息头的大小(40字节)\\+颜色表项*4字节+图像的宽度*高度*位深度/8(字节)






    b


    f


    S


    i


    z


    e




    =



































    1


    4













    +



































    4


    0



















    +































    4










    +




























































    /


    8



















    我们打开grey8.bmp的属性,点击详细信息,可以看到grey8.bmp的大小为256×192像素,位深度为8,又因为其位深度为8,故其颜色表项有256项(每项占据4字节),故grey8.bmp的大小为:





    b

    f

    S

    i

    z

    e

    =

    14

    +

    40

    +

    256

    4

    +

    256

    192

    8

    /

    8

    =

    50230

    (

    )

    bfSize=14+40+256*4+256*192*8/8=50230(字节)






    b


    f


    S


    i


    z


    e




    =








    1


    4




    +








    4


    0




    +








    2


    5


    6













    4




    +








    2


    5


    6













    1


    9


    2













    8


    /


    8




    =








    5


    0


    2


    3


    0


    (








    )





  • 再后面的两个字节(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 位图信息头

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7pzsYKPp-1573204554264)(img/BITMAPINFOHEADER .png)]

  • 头四个字节(第一行的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,其实就是计算像素阵列的大小,计算方式为:





    b

    i

    S

    i

    z

    e

    I

    m

    a

    g

    e

    =

    /

    8

    biSizeImage=图像的宽度*高度*位深度/8






    b


    i


    S


    i


    z


    e


    I


    m


    a


    g


    e




    =




























































    /


    8







    在grey8.bmp图像中





    b

    i

    S

    i

    z

    e

    I

    m

    a

    g

    e

    =

    256

    192

    8

    /

    8

    =

    49152

    biSizeImage=256*192*8/8=49152






    b


    i


    S


    i


    z


    e


    I


    m


    a


    g


    e




    =








    2


    5


    6













    1


    9


    2













    8


    /


    8




    =








    4


    9


    1


    5


    2





  • 后四个字节(第三行的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个字节(只显示了一部分):

在这里插入图片描述

由于每个颜色表项为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灰度图的颜色表项修改为随机值,将原灰度图变为伪彩色图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPGLoDqK-1573204554267)(img/grey8_test-1572943023710.bmp "grey8_test.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 结果

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CPtn37fu-1573204554269)(img/1.bmp)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eTnc5MNE-1573204554271)(img/2.bmp)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7PEO2f0U-1573204554273)(img/3.bmp)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PMaqaxgH-1573204554275)(img/4.bmp)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7bQn5VRs-1573204554277)(img/5.bmp)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oMHLZ8xG-1573204554281)(img/pseudo.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格式”的介绍



版权声明:本文为qq_41261251原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。