BMP文件格式解析
BMP文件格式解析
作者:水木子
文章目录
一、图像概述
1.1 位图
位图(Bitmap),又称栅格图(英语:Raster graphics)或点阵图,是使用像素阵列来表示的图像。 ----*
简单地说,位图就是由一个一个像素点构成的图像。常见的图像格式有jpg(jpeg)、png、bmp都是位图。
1.2 矢量图
矢量图形是计算机图形学中用点、直线或者多边形等基于数学方程的几何图元表示图像。
----*
矢量图与位图不同,它不是由一个一个像素点构成的,它的实质是数学表达式。svg格式文件是矢量图。
1.3 位图与矢量图的区别
位图与矢量图最明显的区别是:位图放大会出现马赛克,画质变差;矢量图可以在不降低画质的条件下无限放大。
图像来源:*
图中a表示原图像。若a为矢量图,则放大红框中图像时,效果如b,可以看见图像质量并没有下降;如果a是位图,则放大红框中图像时,效果如c,可以明显看见有一个一个小方格,图像质量明显下降。
1.4 如何表示像素点的颜色
选择一张位图,在PS中放大到3200%,如下所见:
可以很明显得看见一个一个的小方块,这就是像素点。
一个像素点有特定的位置和颜色值。每个像素的颜色由RGB组合或灰度值表示。
本小节重点介绍如何表示颜色。
根据位深度,可以将位图分为1、4、8、16、24及32位图像等。这里的位深度是指用于表示像素点颜色的比特位个数。如果一个像素点由一个比特位表示颜色,那么其位深度为1,如果一个像素点由四个比特位表示颜色,则其位深度为4,依次类推。
- 如果一张图片的像素点由1个比特位来表示颜色,则这个比特位为0或1,则可以表示21种颜色,即黑与白,则这张照片是纯粹的黑白照片。
-
如果一张图片的像素点由8个比特位来表示颜色,则这八个比特位共可以表示28种颜色,即256。这种图像通常(有例外,下面会讲)称为灰度图,因为这256种颜色是黑白灰(这里的灰是指244种不同程度的灰)。如下为灰度图:
-
如果一张图片的像素点由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时,并不一定代表这张图片为灰度图,如下:
这张图片的位深度为8,但其并不是灰度图,我们称之为伪彩色图。
下面这张图是位深度为24的真彩色图,可以作为对比:
可以看出真彩色图的质量明显高于伪彩色图。
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,如果没有可自行选择插件管理进行安装,最终界面如下:
这是以十六进制的形式显示图像信息,一个十六进制数字占4个比特位,故一行表示十六个字节。
在对BMP文件进行具体解析前,我们还要先了解一下数据的存放顺序:
在BMP文件中,如果一个数据需要用几个字节来表示的话,那么该数据的存放字节顺序为“低地址存放低位数据,高地址存放高位数据”。如数据0x1756在内存中的存储顺序为:
这种存储方式称为小端方式(little endian) , 与之相反的是大端方式(big endian)。
3.1 位图文件头
下图红框中为位图文件头:
-
头两个字节(0,1)表示位图文件的类型,即
0x4d42
表示类型,为BMP,这与DUMP中的头两个字节表示的符号BM一致
。 -
后面的四个字节(2,3,4,5)表示位图文件的大小,即
0x0000c436
表示位图文件大小,换算为十进制为50230,我们打开grey8.bmp的属性,发现其大小确为50230字节:位图文件的大小,即bfSize的值需要如何计算呢?
我们打开grey8.bmp的属性,点击详细信息,可以看到grey8.bmp的大小为256x192像素,位深度为8,又因为其位深度为8,故其颜色表项有256项(每项占据4字节),故grey8.bmp的大小为: -
再后面的两个字节(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 位图信息头
-
头四个字节(第一行的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,其实就是计算像素阵列的大小,计算方式为:
在grey8.bmp图像中 -
后四个字节(第三行的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灰度图的颜色表项修改为随机值,将原灰度图变为伪彩色图。
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 结果
利用我们的程序,生成了以下图片,还挺漂亮的!
3.7 完整代码
四、扩展实验
- 编写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格式”的介绍