BMP格式分析

C#下将BMP转换成二进制是比较容易的,方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
byte[] bufPic;
string filePath = "";
Stream myStream;
MemoryStream picFile = new MemoryStream();
OpenFileDialog open = new OpenFileDialog();
open.Filter = "bmp files (*.bmp) | *.bmp";
open.RestoreDirectory = true;
if (open.ShowDialog() == DialogResult.OK)
{
if ((myStream = login.OpenFile()) != null)
{
myStream.Close();
}
}

filePath = open.FileName.ToString();
Image image = Image.FromFile(filePath);
image.Save(picFile, ImageFormat.Bmp);
bufPic = picFile.GetBuffer();

其实转换的结果是以8位二进制分割的十进制数(0~255),若是真要得到纯二进制,还需要进一步转换。我曾做过很多实验,目的是测试这些数字和图片本身之间的关系,虽然学习过多媒体技术,但是对于详细的bmp分解,并没有太多认识。当然,在实验前还要把”二进制”还原bmp的方法说明一下:

1
2
3
4
5
6
7
8
using (MemoryStream ms = new MemoryStream(myPic))
{
Image image1 = Image.FromStream(ms);
ms.Close();
pic.Image = image1;
}

//其中mypic是byte类型,存放"二进制"的数组,pic是PictureBox控件

实验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进制来考虑,这也符合计算机的设计原理。

通过很多测试,最后得出了一些结论,参考了相关的资料并询问了导师,现总结如下:

  1. 位图文件可看成由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、彩色表(color table)和定义位图的字节阵列

  2. 四个部分在位图图像数据中的相应位置,(位置偏移均以位图数据开始处为基准)

    起始位置偏移 <= 各部分数据具体存放位置 < 结束位置偏移

    第一部分,图像头:

    起始位置偏移 0,

    长度:0x0EH (2byte + 3 * dword = 14)

    结束位置偏移:起始位置偏移 + 长度

    第二部分,图像信息头:

    起始位置偏移:上一部分结束位置偏移

    长度:从 0x0EH 处读取到的 dword 的数据值

    结束位置偏移:起始位置偏移 + 长度

    第三部分,调色板:

    起始位置偏移:上一部分结束位置偏移

    长度:从 0x0AH 处读取到的 dword 的数据值- 起始位置偏移

    结束位置偏移:起始位置偏移 + 长度

    第四部分,位图数据:

    起始位置偏移:上一部分结束位置偏移

    长度:从 0x22H 处读取到的 dword 的数据值

    结束位置偏移:文件结束

  3. 单色位图图像数据的表示方法

    在单色位图图像中,只有两种颜色,黑色或白色,每一个像素只需要一个比特就能够完成表示,为了清楚比特0或1具体表示哪一种颜色,可以通过查询调色板。

    在单色位图图像中,调色板只包含两种颜色,每一种颜色用R G B 0 四个字节表示 (在实际的字节流中,顺序是 B G R 0)

    所以,位图图像数据中的0 代表调色板中 第一种颜色的颜色值, 1 代表调色板中 第二种颜色的颜色值。

  4. C/C++中数据类型的长度

    byte : 1个字节, 8位(比特)

    word: 2个字节, 由 unsigned short定义

    dword:4 个字节, 由 unsigned long定义

  5. 根据前面的位图文件结构表,可以通过自定义数据结构 struct的方式来读取 相应的数据。

  6. 位图数据的存储方式:(自下而上,从左到右)

    扫描行是由底向上存储的,这就是说,位图数据的第一个字节表示位图左下角的象素,而最后一个字节表示位图右上角的象素。

  7. 一行单色位图数据的存储格式规定

    每一扫描行的字节数必需是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左下角的像素。

作者

Wu Rang

发布于

2011-01-20

更新于

2024-06-13

许可协议

评论