BMP 文件格式


File Format, Image File Format

目录

  1. BMP 是什么
  2. 自己设计一个图片文件格式
  3. BMP 文件格式
    1. 文件头
    2. 位图信息头
    3. 调色板
    4. 像素数据区
  4. 动手制作一个 BMP 文件

BMP 是什么

BMP 是一种图像文件格式,取自位图(Bit MaP)的缩写,扩展名为.bmp,是微软搞出来的。这种格式对图像几乎是不经过压缩的,所以文件大小会非常地大,不适合用来在因特网上传输信息。

自己设计一个图片文件格式

文件存在电脑里都是一些二进制数,例如有个文件hello.txt,在里面写了helloworld,这些字符串存在电脑里就是:

$ od -tax  hello.txt
0000000    h   e   l   l   o   w   o   r   l   d
                 6c6c6568        726f776f        0000646c
0000012

可以看到h对应0x68e对应0x65l对应0x6co对应0x6fw对应0x77r对应0x72r对应0x6cd对应0x64 这些文本的编码方式是 UTF-8,而 UTF-8 是和 ASCII编码兼容的,即前 128 个字符都是用单字节来表示,所以这里的英文字符对应 ASCII 编码。

和在文本文件里写字符串不同,图片文件写的颜色,所以图片就是颜色的集合,如何用二进制数来表示颜色呢?

最简单的办法就是用 3 个字节表示一个颜色,分别对应红绿蓝三原色,例如0xff0000表示红色、0x00ff00表示绿色、0x000000表示黑色。

那么一张 4x4 从黑色到白色的图片就可以表示为:

000000 111111 222222 333333
444444 555555 666666 777777
888888 999999 aaaaaa bbbbbb
cccccc dddddd eeeeee ffffff

最后可以再给这个文件追加两个0x00字节,表示文件结束。这样一来,这个文件的大小就是 4 * 4 * 3 + 2 = 50 个字节。假如有张图片的大小为 1080x1920,那这个文件的大小就变成了 1080 * 1920 * 3 + 2 = 6220802 字节 = 6 MB!也就是说看一张超清的图片需要 6 MB 的流量!很显然,我刚刚设计的这种图片文件格式非常地不合理。

BMP 文件格式

其实 BMP 文件格式和上面的那种格式差不多,不过 BMP 使用到了一个称为 调色板 的概念,类似索引,利用“索引”,我们可以省下一部分空间,不过文件大小还是有点大。

BMP 文件可以分为 4 个区域:

  1. 文件头
  2. 位图信息头
  3. 调色板
  4. 像素数据区

下面分别来说说各个部分的含义。

文件头

首先的 14 字节是文件头,存储了文件的一些通用信息。其中包括:

字段字节数说明
文件签名,即 magic number2相当于一个文件的标识符,通常是0x42 0x4d,即 ASCII 码的 “BM” 字符
文件大小4也就是说 BMP 文件的大小最大为 $2^{8*4} = 2^{32} = 4\text{(GB)}$
保留4以后可能有用
像素数据区的偏移量4可以看做是一个指针,表示最后的像素数据区在整个文件中的字节偏移量

位图信息头

随后是位图信息头,常见的为 40 字节:

字段字节数说明
位图信息头的总大小4例如,如果位图信息头占 40 个字节,则这里应该是0x00000028
图像宽度4单位为像素
图像高度4单位为像素
色彩平面数2只能是 1
色深2表示每个像素的位数,即在像素数据区中,一个颜色记录占多少位,常见的值有 1、4、8、16、24 和 32
压缩方法4通常为 0,即不压缩
像素数据区的大小4表示最后的像素数据区的字节大小
图像的横像分辨率4单位为像素/米,常见为0x00000b12,即 2834 像素/米
图像的纵向分辨率4和图像的横像分辨率一样
调色板中颜色的个数4也就是“索引”的数量,值为 0 时表示颜色个数为 $2^{\text{色深}}$ 个
重要的颜色数量4通常为 0,即所有的颜色都重要

调色板

随后是调色板,调色板中包含的是颜色记录,一条颜色记录可以表示一种颜色,每条颜色记录占 4 个字节。例如0xff000000表示蓝色、0xffff0000表示青色。所以调色板的大小为 $\text{颜色数} * 4 \text{(B)}$,如果用到的颜色只有一种的话,那就只用占 1 * 4 个字节。

调色板的颜色记录数是可变的,可以根据 $\text{文件头中的位图数据偏移量} - 52$ 算出调色板的字节数,然后除以 4 得到颜色记录数。

像素数据区

例如调色板的数据为0xff0000000x00ff00000x0000ff00,而像素数据为0x000x020x000x01则表示为 蓝色、红色、蓝色、绿色,颜色索引的位数由色深指定。

整个文件如下:

./assets/images/bitmapfileformat.png
.bmp 文件格式

文件的最后的 2 个表示文件结束的字节0x0000,也可以不加。

动手制作一个 BMP 文件

知道了文件的格式,我们便可以新建一个空的文件,然后再一个字节一个字节地将数据写入文件里:

with open('a.bmp', mode='wb') as f:
    # 定义文件头,一共 14 字节
    FILE_HEADER = bytes([
        0x42, 0x4d,              # ASCII 的 "BM"
        0x38, 0x05, 0x00, 0x00,  # 文件的大小:4 字节
        0x00, 0x00,              # 保留的字节
        0x00, 0x00,              # 保留的字节
        0x36, 0x04, 0x00, 0x00   # 像素数据区的偏移量
    ])
    f.write(FILE_HEADER)

    # 位图信息头,一共 40 字节
    FILE_INFO = bytes([
        0x28, 0x00, 0x00, 0x00,  # 位图信息头大小:40 个字节
        0x10, 0x00, 0x00, 0x00,  # 图像的宽度,单位为像素:16 像素
        0x10, 0x00, 0x00, 0x00,  # 图像的高度:16 像素
        0x01, 0x00,              # 色彩平面数:只能是 1
        0x08, 0x00,              # 每个像素的位数:8位
        0x00, 0x00, 0x00, 0x00,  # 是否压缩:不压缩
        0x00, 0x01, 0x00, 0x00,  # 像素数据区占的字节数
        0x12, 0x0b, 0x00, 0x00,  # 图像的横向分辨率
        0x12, 0x0b, 0x00, 0x00,  # 图像的纵向分辨率
        0x00, 0x00, 0x00, 0x00,  # 调试板的颜色数:为 0 时表示颜色数为 2^{色深} 个
        0x00, 0x00, 0x00, 0x00   # 重要颜色的数量:为 0 时说明所有颜色都重要
    ])
    f.write(FILE_INFO)

    # 调色板,从全黑到全白,共 256 个颜色
    PALETTE_SIZE = 256
    for i in range(PALETTE_SIZE):
        f.write(bytes([i, i, i, 0x00]))

    # 像素数据区
    # 注意:数据是从图像左下角往右上角写的。
    # 也就是说左下角为黑色,右上角为白色。
    IMAGEDATA_SIZE = 256
    for i in range(IMAGEDATA_SIZE):
        f.write(bytes([i]))

    f.write(bytes([0x00, 0x00]))  # 结束标志

    f.flush()
    f.close()

最后的结果就是一个 16x16 的位图图像文件:

./assets/images/create_bitmap_file_result.png
生成的 .bmp 文件

需要注意的是:像素数据区的从图像的左下角开始,往右写的,所以最后左下角是黑色,因为我们一开始写入的是0x00

位图文件格式还是很容易理解,不过这种文件并不适合用来在互联网上传输,因为图像的颜色信息几乎是没有压缩过的,所以只要文件尺寸大一些,文件大小会很大。

例如一张 256x256 的灰度图就需要 54 + (4 * 256 + 512 * 512 * 1) = 263222 (B) ≈ 257 (KB)! 这还只是一张 256x256 的黑白照片,而一张 400x400 的 JPEG 格彩色图片文件也不过 25730(B) ≈ 25(KB), 具体的大小要看 JPEG 的压缩率。

所以网络上大多数都是 JPEG、PNG 这类格式的图片,更省流量。

JPEG 使用到了小波变换来滤掉图片的高频部分,然后再进行编码,所以文件相对就会小很多,而 BMP 格式的文件更加适合用来做研究,因为 BMP 文件几乎是没有压缩过的。


Disqus 评论加载中...