最近在提取《基连的野望 阿克西斯V》的MRG文件时接触了BMP图片,于是从底层对BMP图片进行研究,学习BMP图片的显示和成像原理。
BMP图片简介
BMP图片是一种栅格图片,BMP图片的每一个像素都由一个比特位(bit)或多个比特位(bits)表示,因此BMP全称是bitmap 或 a map of bits and pixels。
BMP是Windows操作系统中的标准图像文件格式,也是众多早期游戏广泛应用的图片格式,从游戏贴图到游戏字幕都可以是BMP图片。
BMP是一种没有经过压缩的图片格式,也就是说,BMP图片的每一个像素有自己的比特位(或一组比特位),常见的PNG或JPEG则不同,这些是经过压缩技术对图片的信息进行压缩,以达到减小图片体积的目的。因此多数情况下,显示相同的图像,BMP图片的大小总是最大的。
BMP图片组成
本文使用以下BMP图为例进行说明,图片另存为可将图片保存到本地。 使用十六进制打开图片,看到部分图片信息如下: 看似杂乱无章的显示,其实bmp图片由以下四部分组成:
- 文件头(bit map file header): 共14字节
- 位图信息头(bitmap information): 共40字节
- 调色板(color palette): 可选
- 位图数据(pixel data): 可选
下面将对每部分进行详细解读。
注:本文涉及16进制读取均使用小端模式(little-endian)。
第一部分:文件头(bit map file header)
此部分一共包含14个字节,例子中的图片对应的内容为:
一共5个字段,每个字段如下:
字段名 | 大小 | 描述 |
---|---|---|
FileType(文件类型) | 2 bytes | 文件类型,BMP两字节是0x4D42,ASCII字符对应为“BM” |
FileSize(文件大小) | 4 bytes | 文件大小,对应的值单位为字节。例子中的值为0xC016,十进制为49174字节,对应为49KB。 |
Reserved(保留位) | 2 bytes | 2位保留位预留给图片渲染应用,初始化必须为0 |
Reserved(保留位) | 2 bytes | 同上 |
PixelDataOffset(从头到位图数据的偏移) | 4 bytes | 表示从文件头到位图数据的偏移,例子图片的值是0x00000436,十进制是1078,这个值在第三部分调色板会进行验证 |
第二部分:位图信息头(bitmap information)
此部分一共包含40个字节,例子中的图片对应的内容为:
一共11个字段,每个字段如下:
字段名 | 大小 | 描述 |
---|---|---|
HeaderSize | 4 bytes | 信息头大小,即0x28,十进制为40 |
ImageWidth | 4 bytes | 图片宽度,单位像素。例子图片为0x0120,十进制为288,即288px,与实际符合 |
ImageHeight | 4 bytes | 图片高度,单位像素。例子图片为0x00A7,十进制为167,即167px,与实际符合 |
Planes | 2 bytes | 目标设备说明颜色平面数,总被设置为1 |
BitsPerPixel | 2 bytes | 定义颜色的每个像素有多少位,也称为颜色深度,简称bpp,值有1、2、4、8、16、24、32。例子图片值为0x0008,表示每个像素用8位表示,一共有2^8 = 256个颜色 |
Compression | 4 bytes | 图像的压缩类型,最常用的就是0(BI_RGB),表示不压缩 |
ImageSize | 4 bytes | 位图数据的大小,当用BI_RGB格式时,可以设置为0 |
XpixelsPerMeter | 4 bytes | 水平分辨率,单位是像素/米,有符号整数 |
YpixelsPerMeter | 4 bytes | 垂直分辨率,单位是像素/米,有符号整数 |
TotalColors | 4 bytes | 位图使用的调色板中的颜色索引数,为0说明使用所有 |
ImportantColors | 4 bytes | 对图像显示有重要影响的颜色索引数,为0说明都重要 |
第三部分:调色板(color palette)
调色板就是颜色的索引,但调色板是可选的。由于每个颜色都有RGB三原色,也就是要3个字节表示(例如白色表示为#FFFFFF),当bpp≤8时,2^bpp 所计算出来的颜色就无法表示全部的颜色,因此对于bpp≤8的图片就必须使用调色板,下表展示各种位图的调色板情况:
BitsPerPixel(bpp) | 调色板 | 颜色数量 | 描述 |
---|---|---|---|
1 | 有 | 2 | 调色板中最多只能存在2种颜色 |
4 | 有 | 8 | 调色板中最多只能存在8种颜色 |
8 | 有 | 256 | 调色板中最多只能存在256种颜色 |
16 | 无 | 65536 | 可直接使用RGB三原色,每种原色用5bit表示 |
24 | 无 | 16M (16777215) | 可直接使用RGB三原色,每种原色用8bit表示 |
32 | 无 | 4B (4294967296) | 可直接使用RGBA,每种原色用8bit表示,加8bit Alpha |
例子图片中bpp为8,因此每个像素只能有256钟颜色,为了增加图片表现形式,就需要一个索引,使用一个字节表示索引指向4个字节(RGB加Alpha值)。如果把这4个字节表示为一个Color类型,那么调色板就是Color的数组。由于Color类型也是一个数组,调色板就像一个二维数组palette[N][4],其中N是颜色的数量,这里就是256。因此,这个例子中的调色板的大小就是256x4=1024字节,在调色板之前,有14字节的bmp文件头,40字节的位图信息头,加上1024字节的调色板,一共1078字节,也就是说真正的图像数据前面有1078字节,这和bmp文件头中的bfOffBits相符。
第四部分:位图数据(pixel data)
最后就是位图数据,该区域存放的是每个像素的颜色值,以行的形式展示,这个取决于图片的颜色深度(bpp)。同时BMP图片扫描引擎的最小单位是4字节,因此在扫描每一行时,要求每一行的数据长度必须是4的倍数,不足的行将进行比特填充(bit padding)
。
BMP扫描
当图片引擎读取了一张BMP图片,从文件头信息读取数据后,引擎就知道该如何扫描BMP数据,进而把图像展示在用户面前。BMP图像扫描使用从左下角开始到右上角,以行为主序
进行扫描。下图以一个4px X 4px的图像为例:
构建24位BMP图
经过以上图片组成的详细解说了,相信读到这里的你已经以上众多字段忘的差不多了,没关系,接下来我们将通过自己编写十六进制,运用上述原理自己构建一个5px X 5px的24位bmp图像。
首先初始化文件头
(第一部分)和位图信息头
(第二部分),这两部分一共占据14+40=54个字节。初始化数据如下:
字段 | 大小 | 值 |
---|---|---|
FileType | 2 bytes | 0x42 0x4D |
FileSize | 4 bytes | 0x00 0x00 0x00 0x00 |
Reserved | 2 bytes | 0x00 0x00 |
Reserved | 2 bytes | 0x00 0x00 |
PixelDataOffset | 4 bytes | 0x36 0x00 0x00 0x00 |
HeaderSize | 4 bytes | 0x28 0x00 0x00 0x00 |
ImageWidth | 4 bytes | 0x05 0x00 0x00 0x00 |
ImageHeight | 4 bytes | 0x05 0x00 0x00 0x00 |
Planes | 2 bytes | 0x01 0x00 |
BitsPerPixel | 2 bytes | 0x18 0x00 |
Compression | 4 bytes | 0x00 0x00 0x00 0x00 |
ImageSize | 4 bytes | 0x00 0x00 0x00 0x00 |
XpixelsPerMeter | 4 bytes | 0x00 0x00 0x00 0x00 |
YpixelsPerMeter | 4 bytes | 0x00 0x00 0x00 0x00 |
TotalColors | 4 bytes | 0x00 0x00 0x00 0x00 |
ImportantColors | 4 bytes | 0x00 0x00 0x00 0x00 |
初始化数据中多数字段都设置为0,有一些非0数据这里做下解释:
FileType
: 固定值0x42 0x4D,表示BM。PixelDataOffset
: 从头到位图数据的偏移,一共是54bytes,使用十六进制表示即为0x36。HeaderSize
: 文件头大小,同40bytes,十六进制表示为0x28。ImageWidth
: 图片宽度,5px,十六进制表示为0x05ImageHeight
: 图片高度,5px,十六进制表示为0x05Planes
:固定值1BitsPerPixel
:bpp,24位图,值为24,十六进制表示为0x18
将上述数据随便写入一个文本编辑器中,如下:
42 4D
00 00 00 00
00 00
00 00
36 00 00 00
28 00 00 00
05 00 00 00
05 00 00 00
01 00
18 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
新建一个test.bmp文件,使用十六进制编辑器打开图片,将以上内容复制到图片中,此时一个24位的bmp图片的基本头已经形成,但此时还无法打开文件,因为还缺少了和颜色相关的位图数据,我们把他补上。
24位图,即每个颜色都可以使用24比特位表示,例如#000000的颜色:
十六进制表示:
00 00 00
-- -- --
R G B
二进制表示:
00000000 00000000 00000000
-------- -------- --------
R G B
由于是5x5的图片,因此每一行需要5x3bytes,但扫描行的原则需要每行字节数为4的倍数,因此最后还需加上1个字节进行填充(0x00),故完整的位图数据如下:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
拼上文件头和文件信息头,完整的图片数据如下:
42 4D
00 00 00 00
00 00
00 00
36 00 00 00
28 00 00 00
05 00 00 00
05 00 00 00
01 00
18 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
将数据写入test.bmp
中,打开图片,就可以看到一个5x5的黑色图:
之后我们来改变下颜色:
FF FF FF 00 00 00 00 00 00 00 00 00 00 FF FF 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 FF 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 FF 00 00 00 00 00 00 00 00 00 FF 00 00 00
可以验证BMP图片扫描顺序为左下到右上
。
构建16位BMP图
16位和24位的区别主要在于位图数据的表现形式不同。16位图是一个像素只有16位表示,即一种颜色只能用16位表示,因此一种三原色只能用5位表示(最高位为0):
[ 0 XXXXX XXXXX XXXXX ]
----- ----- -----
R G B
因此24位的颜色转为16位颜色如下:
RED 0111110000000000 => 01111100 00000000 => 0x7C 0x00
GREEN 0000001111100000 => 00000011 11100000 => 0x03 0xE0
BLUE 0000000000011111 => 00000000 00011111 => 0x00 0x1F
WHITE 0111111111111111 => 01111111 11111111 => 0x7F 0xFF
YELLOW 0111111111100000 => 01111111 11100000 => 0x7F 0xE0
即:每种颜色只能使用2个字节表示
,对于5x5的图片,每行字节数需为4的倍数,因此每行最后还需填充2个字节:
42 4D
00 00 00 00
00 00
00 00
36 00 00 00
28 00 00 00
05 00 00 00
05 00 00 00
01 00
10 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
FF 7F 00 00 00 00 00 00 E0 7F 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 E0 03 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 7C 00 00 00 00 00 00 1F 00 00 00
最后test.bmp
图片显示依旧和24位一致,只是16位图片文件更小了。
总结
本文针对bmp图像进行文件结构解析,并逐一解释内部信息的含义,最后通过十六进制编写的方法实现了24位图和16位图以加强对bmp图像的理解,对bmp图像的描述本文可能有错误或遗漏之处,欢迎反馈和指点。
(完)