BMP格式分析
C#下将BMP转换成二进制是比较容易的,方法如下
1 | byte[] bufPic; |
其实转换的结果是以8位二进制分割的十进制数(0~255),若是真要得到纯二进制,还需要进一步转换。我曾做过很多实验,目的是测试这些数字和图片本身之间的关系,虽然学习过多媒体技术,但是对于详细的bmp分解,并没有太多认识。当然,在实验前还要把”二进制”还原bmp的方法说明一下:
1 | using (MemoryStream ms = new MemoryStream(myPic)) |
实验1: 人为的变动bufPic数组中的前几个位置,得到的结果都是出错,没能正确的还原回bmp,仔细观察后在发现,几乎所有的bmp转换成的bufPIc的前几个位置都相同,所以得到一个猜测:bmp文件不是单一的由图片的像素组成,还有一些默认的统一的”约定”
实验2: 在试验1的基础上挨个尝试,找到变更后能正确还原bmp的位置,由这个位置来确定所谓的”约定”的位数。得到的结论是42个。猜测就是:每个bmp文件都有54个默认的参数。
实验3: 进一步验证参数的个数是否正确。更改54位以后的位置,看看结果对于图片的影响。测试结果如下:
用这个原图片,修改42位以后的若干位,结果是!
由此看来变动是相当大的,最明显的是由一个黑白图变为了彩色图,所以又有一个猜测:默认的参数不仅仅有54位,还包含一些控制调色板的位。
实验4: bufPIc中的每一个数字是否代表了bmp中每一个点的信息?答案当然是否定的,一个32*16的bmp中,有512个点,但bifPic中只有256个,还包括一些默认参数。只能将数字转换成2进制来考虑,这也符合计算机的设计原理。
通过很多测试,最后得出了一些结论,参考了相关的资料并询问了导师,现总结如下:
位图文件可看成由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、彩色表(color table)和定义位图的字节阵列
四个部分在位图图像数据中的相应位置,(位置偏移均以位图数据开始处为基准)
起始位置偏移 <= 各部分数据具体存放位置 < 结束位置偏移
第一部分,图像头:
起始位置偏移 0,
长度:0x0EH (2byte + 3 * dword = 14)
结束位置偏移:起始位置偏移 + 长度
第二部分,图像信息头:
起始位置偏移:上一部分结束位置偏移
长度:从 0x0EH 处读取到的 dword 的数据值
结束位置偏移:起始位置偏移 + 长度
第三部分,调色板:
起始位置偏移:上一部分结束位置偏移
长度:从 0x0AH 处读取到的 dword 的数据值- 起始位置偏移
结束位置偏移:起始位置偏移 + 长度
第四部分,位图数据:
起始位置偏移:上一部分结束位置偏移
长度:从 0x22H 处读取到的 dword 的数据值
结束位置偏移:文件结束
单色位图图像数据的表示方法
在单色位图图像中,只有两种颜色,黑色或白色,每一个像素只需要一个比特就能够完成表示,为了清楚比特0或1具体表示哪一种颜色,可以通过查询调色板。
在单色位图图像中,调色板只包含两种颜色,每一种颜色用R G B 0 四个字节表示 (在实际的字节流中,顺序是 B G R 0)
所以,位图图像数据中的0 代表调色板中 第一种颜色的颜色值, 1 代表调色板中 第二种颜色的颜色值。
C/C++中数据类型的长度
byte : 1个字节, 8位(比特)
word: 2个字节, 由 unsigned short定义
dword:4 个字节, 由 unsigned long定义
根据前面的位图文件结构表,可以通过自定义数据结构 struct的方式来读取 相应的数据。
位图数据的存储方式:(自下而上,从左到右)
扫描行是由底向上存储的,这就是说,位图数据的第一个字节表示位图左下角的象素,而最后一个字节表示位图右上角的象素。
一行单色位图数据的存储格式规定
每一扫描行的字节数必需是4的整倍数,当不够4的整数倍时,需要加0补齐
以 720 × 450 的单色位图图像为例
水平扫描行的长度为720,则需要720比特来表示一个扫描行,即需要 720/8=90字节来表示,但是 90 不是 4 的整数倍,因此需要用0补齐,直至为4的整数倍,即需要额外的2个填充字节。最终,长度为720的水平扫描行使用了 92 个字节来表示。
例1:t1图为50×50的单色图,水平扫描行的长度为50,则需要50比特来表示一个扫描行,即需要 50/8=7字节来表示,但是 7 不是 4 的整数倍,因此需要用0补齐,直至为4的整数倍,即需要额外的1个填充字节。最终,长度为50的水平扫描行使用了 8个字节来表示。共8×50=400个字节,再加上62个字节的头部,既为文件大小462个字节。
例2:t3图为40×40的单色图,水平扫描行的长度为40,则需要40比特来表示一个扫描行,即需要 40/8=5字节来表示,但是 5 不是 4 的整数倍,因此需要用0补齐,直至为4的整数倍,即需要额外的3个填充字节。最终,长度为40的水平扫描行使用了 8个字节来表示。共8×40=320个字节,再加上62个字节的头部,既为文件大小382个字节。
例3:t5图为16×32的单色图,水平扫描行的长度为16,则需要16比特来表示一个扫描行,即需要 16/8=2字节来表示,但是 2 不是 4 的整数倍,因此需要用0补齐,直至为4的整数倍,即需要额外的2个填充字节。最终,长度为16的水平扫描行使用了 4个字节来表示。共4×32=128个字节,再加上62个字节的头部,既为文件大小190个字节。
因此水平像素应为4个字节(32个像素)的整数倍最好。空间利用率高,否则需要补零。
最后要说的是:bmp扫描是有底向上存储,即第一个字节表示bmp左下角的像素。