Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1792247
  • 博文数量: 306
  • 博客积分: 3133
  • 博客等级: 中校
  • 技术积分: 3932
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-19 16:50
文章分类

全部博文(306)

文章存档

2018年(7)

2017年(18)

2016年(39)

2015年(35)

2014年(52)

2013年(39)

2012年(22)

2011年(29)

2010年(53)

2009年(12)

分类: 嵌入式

2010-05-14 18:28:05

BMP格式详解

前言

记得本科时候讲《计算机体系结构》的老师(很遗憾忘了他姓名)评价过中外教材的差别,他说按照老外的体系结构教材,你就真的能够做出一个CPU来 (虽然只能做出很老很老的CPU),但国内的教材就很难教到这个程度。

几个月前我从零开始写了一个简单的bmp解码库,现在用一篇文章把其中的关键内容记录下来,希望能够达到让别人照着文章就可以开发出任何语言绑定的 bmp解码库的程度,以便日后我可以放心地忘却,因为我的脑子里总是不能同时记住太多的东西。 

BMP简介

BMP是一种与硬件设备无关的图像文件格式,使用非常广。BMP是Windows环境中交换与图有关的数据的一种标准,在Windows环境中运行 的图形图像软件都支持BMP图像格式。

相对来讲,BMP格式比较简单,它只包含两个重要参数:编码格式(Encoding)和像素位数(bpp, bit-per-pixel)。到目前为止,BMP格式所支持的所有像素位数与编码格式的组合如下:

序号

像素位数(bpp)

编码格式(Encoding)

1

1

bit

2

4

bgr(blue-green-red)

3

4

rle(run-length encode)

4

8

bgr

5

8

rle

6

grayscale

bgr

7

grayscale

rle

8

16

bgr

9

16

bitfields-555

10

16

bitfields-565

11

16

bitfields-customized

12

24

bgr

13

32

bgr

14

32

bitfields-888

15

32

bitfields-customized

其中24bpp称为真彩(true-color)图像,应用最为广泛。16bpp的bmp图像拥有存储空间小,解析速度快,仿真彩效果好等特点,经 常出现在游戏软件中。grayscale(灰度)图像其实是8bpp的一种情况。 

文件结构

典型的BMP图像文件由四部分组成:
1:文件头,它包含BMP图像文件的类型、内容尺寸和起始偏移量等信息;
2:图像参数,它包含 图像的宽、高、压缩方法,以及颜色定义等信息;
3:调色板,可选部分,bpp较小的位图需要调色板;有些位图,比如24bpp(真彩色)图就不需 要调色板;
4:位图数据,这部分的内容因位图实际像素位数和编码格式而不同,在真彩位图中直接使用RGB真彩色值;而有调色板的位图则使用调色板 中颜色索引值。 

数据类型

先定义几个数据类型以便于描述。
byte —— 8 bits
word —— 16 bits
int/uint/dword —— 32 bits 

文件头

BMP的文件头共14个字节。

字节顺序

数据结构

描述

1,2

word

高8位为字母’B’,低8位为字母’M’

3,4,5,6

uint

文件尺寸

7,8

word

保留字1

9,10

word

保留字2

11,12,13,14

uint

位图数据部分相对于文件首的起始偏移量

数据部分偏移量的存在,说明图像数据部分并不一定要紧随图像参数或调色板之后放置,BMP图片的制作者其实可以在调色板之后、数据部分之前填充任何 内容,只要正确地设置偏移量即可。 

图像参数信息

这一个数据块共40字节或56字节。前40字节的内容如下:

字节顺序

数据结构

描述

15,16,17,18

uint

当前结构体的大小,通常是40或56

19,20,21,22

int

图像宽度(像素)

23,24,25,26

int

图像高度(像素)

27,28

word

这个字的值永远是1

29,30

word

每像素占用的位数,即bpp

31,32,33,34

uint

压缩方式

35,36,37,38

uint

图像的尺寸(字节数)

39,40,41,42

int

水平分辨率,pixels-per-meter

43,44,45,46

int

垂直分辨率,pixels-per-meter

47,48,49,50

uint

引用色彩数

51,52,53,54

uint

关键色彩数

水平分辨率和垂直分辨率我从来没用过。看上去应该是与设备相关的参数。

如果你是一个有优化癖的程序员,你一定会问,图像的宽和高为什么是int型而不是uint型呢?因为想象中负数宽和高似乎没有意义。比较滑稽的是, 在BMP格式中,负数的高是有意义的。为了与高搭配,因此图像的宽也定义为int型。负数高的意义我们将在图像数据块中讨论。

第31-34字节存储着一个uint型参数,它代表图像数据的压缩方式。该参数的取值范围是0、1、2或3等等。这些取值的含义分别是:

0 —— RGB方式
1 —— 8bpp的run-length-encoding方式
2 —— 4bpp的run-length-encoding方式
3 —— bit-fields方式

只有压缩方式选项被置为bit-fields时,当前结构体的大小为56字节,否则为40字节。 

调色板

当bpp小于等于8时,BMP使用调色板记录色彩信息,调色板中每条数据(即每种色彩值)都是一个uint型数据。当调色板存在时,图像数据块中存 储的只是各个像素的色彩在调色板中的索引值,必须通过在调色板中查表,才能获知各个像素的真实颜色。若引入调色板,则调色板数据块紧随在图像参数数据块之 后。

当bpp == 1时,调色板合法索引值只有0和1。因此调色板中只有2个色彩值,分别表示索引值为0和1时的色彩信息。

当bpp == 4或bpp == 8时,合法索引值范围扩大为[0,15]和[0,255]。但图像中不一定使用到了全部16种或256种颜色。第47-50字节存储的uint型数据指出 图像中实际应用的色彩数,也即调色板中的色彩值数目。当然,它不应超出调色板的合法索引值的范围。

当bpp == 4或bpp == 8时,可以采用Run-Length-Encoding方式压缩图像的存储空间,即压缩方式选项的值为1或2(当选项值为0时,不压缩)。这种编码格式所 考虑的情况是,若4bpp或8bpp位图的尺寸较大时,由于色彩总数非常有限,所以图像中必然会有很多颜色重复的像素。因此BMP图像格式的设计者决定采 取一个简单的措施来挽回一些被浪费掉的存储空间。这个简单的措施就是RLE压缩方法。 

RLE, Run-Length-Encoding

如果你是一个有优化癖的程序员,当你发现一张8bpp、宽300像素的位图中有一行像素只有两种颜色:前100个像素是红色,后200个像素是蓝 色,然而你的位图却忠实地用100字节来存储前半行重复的红色,又用200字节来存储后半行重复的蓝色,那么你一定会抓狂到大骂BMP格式的设计者是白 痴。

为了避免被骂,BMP格式的设计者想出了这样的办法:先用一个字节来存储重复色彩的数量,再用一个字节来存储这个色彩的值,即用两个字节代表一段颜 色重复的像素。并且,他们给重复色彩的数量起了个名字,叫做Run-Length,可能是因为只有在运行时我们才能知道这段重复色彩的长度。由于 runlength为0时没有意义,因此设计者把runlength=0当做每行的终止符。于是,同样存储一行300个像素,原先需要300字节,现在只 用5个字节就搞定!

字节

1

2

3

4

5

内容

100

red

200

blue

 0 

由于你是一个有着严重优化癖的程序员,所以你对这样粗制滥造的优化方法并不满足,因为你很快发现,如果一张位图中没有连续重复的像素(例如红蓝像素 点阵),那么用刚才发明的这个办法存储300个像素,居然要用601字节!当然,这种情况下最好的办法是不用压缩算法。可是,如果既有重复像素,又有点阵 的情况呢?比如前150像素是重复的绿色,后150像素是红蓝相间的像素点阵。

为了满足你变态的优化癖,BMP格式的设计者只好继续发展这个算法。首先,他们保留了“用一个字节来存储重复色彩的数量,再用一个字节来存储这个色 彩的值”的设计思路,然后修改了runlength为0的含义。设计者规定,当遇到runlength==0时,我们要继续读取下一个字节,若该字节值为 n,意味着后面的n个像素将采用“逐字翻译”的方式来解析,也就是说,这n个像素的前面没有runlength这个字节。用这种方法压缩“前150像素是 重复的绿色,后150像素是红蓝相间的像素点阵”的300个像素,只需要154个字节。

字节

1

2

3

4

后150个字节

内容

150

green

 0 

150

red,blue,…,red,blue

这个近似完美的结果中有个小问题:设计BMP格式的天才们把runlength==0的含义修改后,我们就没有行终结符了。不过天才终归是天才,他 们发现“逐字翻译”的像素数必须大于2才有意义(想想这是为什么?),因此runlength==0之后的那个字节的值为0、1或2时,目前还没有意义。 于是天才们规定,当这个值为0时,表示行结束符;当这个值为1时,表示文件结束符;当这个值为2时,似乎仍然没有什么意义;只有当该值大于等于3时,才是 “逐字翻译”。完整的压缩结果是:

字节

1

2

3

4

后150个字节

155

156

内容

150

green

 0 

150

red,blue,…,red,blue

0

0

这就是传说中的Run-Length-Encoding for 8bpp。4bpp的RLE跟8bpp时没有本质差别。
 

RGBBit-Fields

当图像中引用的色彩超过256种时,我们就需要16bpp或更高bpp的位图。调色板不适合bpp较大的位图,因此16bpp以上的位图都不使用调 色板。不使用调色板的位图图像有两种编码格式:RGB和Bit-Fields(下称BF)。

RGB编码格式是一种均分的思想,使Red、Green、Blue三个颜色分量所包含的信息容量尽可能一样大。

16bpp-RGB:在每个像素所占的16bits中,低5位表示Blue分量;中5为表示Green分量;高5位表示Red分量;最高1位无意义 (后来有些应用程序将其视为透明度Alpha分量,但这并不是标准)。所以从低到高的顺序实际上是B-G-R,这也是我在BMP简介的表格里,把RGB的 编码方式都写成BGR的原因。

24bpp-RGB:24bpp的位图又称为真彩位图,它通常只有这一种编码格式,在24bits中,低8位表示Blue分量;中8为表示 Green分量;高8位表示Red分量。

32bpp-RGB:在32bits中,低24位的编码方式与24bpp位图相同,最高8位用来表示透明度Alpha分量。32bpp的位图尺寸太 大,一般只有在图像处理的中间过程中使用。对于需要半透过效果的图像,更好的选择是PNG格式。

BF编码格式与RGB不同,它利用位域操作,人为地确定RGB三分量所包含的信息容量。在图像参数信息模块的介绍中提及,当压缩方式选项置为BF 时,图像参数结构体将比平时多出16字节。这16字节实际上是4个dword的位域掩码。按照先后顺序,它们分别是R、G、B、A四个分量的位域掩码。当 然如果没有Alpha分量,则Alpha掩码没有实际意义。

位域掩码的作用是:指出像素色彩值中RGB分量,就像子网掩码指出子网网段一样。

16bpp-BF-565:这是BF编码格式最著名和最普遍的应用。它的Red、Green和Blue分量的位域掩码分别是0xF800、 0x07E0和0x001F。

我们平时所能够见到的位图中使用BF编码格式的并不多,因为它看上去比较麻烦,而效果也不见得比RGB要好(你能用肉眼分辨出16bpp-RGB和 16bpp-BF-565之间的区别吗?)。

BF编码格式的重要应用在于游戏软件。游戏软件通常包含数量庞大的小尺寸图片。如果一张图片中几乎没有Blue分量,那么使用16bpp-RGB格 式显然会浪费掉B分量所占的5位。此时若采用16bpp-BF-772格式,只给B分量2位,那么R与G分量都拥有7位的容量,几乎接近真彩图像。因此存 储空间小、仿真彩能力强的特点使BF编码格式仍然有着独特的用武之地。

32bpp-BF-xxx:我一直不明白为什么会存在32bpp的位图。如果说32bpp-RGB格式的存在是因为在图像处理过程中存储起来比较高 效(不用压缩),那么32bpp-BF又是为什么存在呢?

图像数据块

图像数据块从文件头中起始偏移量字段所指出的位置开始,其中存放着位图图像的数据,数据格式由图像参数信息块中的压缩方式选项的取值决定。操作图像 数据块时,有一些注意事项:

当压缩方式为RGB时,图像数据块以“行”为单位双字对齐。例如一张宽度为5像素的8bpp的图像,其实际使用的存储空间是每行8个字节。又如一幅 4bpp、宽度为5像素的位图图像,其实际使用的存储空间是每行4个字节。

当bpp < 8时,每个字节将存放多个像素的色彩索引,则先出现的像素存放在高位中。例如某4bpp图像第一行像素的顺序是red, green, blue, yellow, …则图像数据块中第一个字节的高4位值为red,低4位值为green;第二字节高4位值为blue,低4位值为yellow。1bpp时的情况以此类 推。

还记得前面提到,图像参数里,高度有可能是负值吗?这看上去很逗,但事实是,你见过的大多数位图,其图像参数里的高度都是负值。BMP格式设计者规 定,当高度为正值时,图像数据块中记录的第一行像素数据是图像的最后一行;而数据块中最后一行数据才是实际图像的第一行,也就是说,数据块中的行记录与实 际图像反序。而当高度为负值时,数据块中的行记录与实际图像才是同序的。

如果你觉得这太奇怪了,我很理解。不过我们必须怀着无比崇敬之情接受这个看似滑稽的规定。这是因为在那个年代里,那些设计BMP格式的天才首先都是 数学家,让天才的数学家们习惯左上角为原点,并且y轴方向向下的二维直角坐标系的格局显然是很困难的,既然他们手上又有设计BMP的大权,于是……唉,这 就是历史。

阅读(6949) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

chinaunix网友2010-07-16 14:42:33

“逐字翻译”的像素数必须大于2才有意义? 100红 1绿 100蓝 100红 用上面说的8bpp的RLE怎么表示?