调色板
当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时没有本质差别。
RGB与Bit-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的大权,于是……唉,这 就是历史。