1 前言
平时网络上有非常多类型的图片,最常见的就是png与jpg类型,但由于它们内部做了很多算法的处理、对图片数据进行了一定程度的压缩,所以并不适合一开始就拿来学习。
而本文要介绍的BMP图片,便是一种非常原始的图片格式,它将每个像素的数据都完整的保存了下来,非常适合新手学习。
2 BMP格式概览
BMP文件格式还是比较简单的,总共分为四部分:
- BITMAPFILEHEADER结构体,
- BITMAPINFOHEADER结构体,
- RGBQUAD结构体(这个结构体可以有,也可以没有),
- DIB数据区。(- Device-Independent Bitmap,设备无关位图)。
在继续探究这些格式之前,我们还需要明白一个概念,即:RGB(Red,Green,Blue)三原色,我们图片中的像素就是用这三种颜色构成的
我们常看到的8位色,16位色,24位色和32位色这些,实际指的就是用多少位来表示一个颜色(又称位深度)
位指的是二进制的一位,一个字节(char)有8位:

以8位色为例,即一个char,可以表示的范围为:0 - 28-1,即256种颜色,如果是1位色,那就只能表示两个颜色,即黑白图片。
目前主流的是24位色和32位色,所以这里不讨论8位数和16位色
事实上第三个结构体RGBQUAD就是为8位、16位色等较低位数准备的,使用起来也更麻烦。
本文以24位色为主。即RGB三原色,各占8位大小,即各占一个字节。
如果是32位,则RGB三原色各占8位,还剩下8位用于描述透明度的,即:rgba最后的a,Alpha透明度。
综上,我们需要了解的就只有前两个结构体,和最后的DIB数据区了
首先来看第一个结构体定义:
typedef struct tagBITMAPFILEHEADER {
        WORD    bfType; //图片种类,BMP图片固定为BM,表示为十六进制就是0x4d42
        DWORD   bfSize; //该图片文件的大小
        WORD    bfReserved1; //保留字,不用管
        WORD    bfReserved2;//保留字,不用管
        DWORD   bfOffBits; //实际图片数据的偏移量,即`DIB`的偏移量,也即前三个结构体的大小
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
该结构体中,我们需要在意的只有两个属性:bfSize与bfOffBits,分别等于该图片文件的大小,以及DIB数据区前三个结构体的大小
然后是第二个结构体:
typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize; //指定此结构体的长度
        LONG       biWidth; //bmp图片的宽度
        LONG       biHeight; //bmp图片的高度
        WORD       biPlanes; //平面数,显示器只有一个平面,所以一般为1
        WORD       biBitCount; //颜色位数,目前一般用24位或32位
        DWORD      biCompression; //压缩方式,可以是0,1,2,0表示不压缩,BMP为不压缩,所以为0
        DWORD      biSizeImage; //实际位图数据占用的字节数.由于上面不压缩,所以这里填0即可
        LONG       biXPelsPerMeter; //X方向分辨率,即每米有多少个像素,可以省略
        LONG       biYPelsPerMeter; //Y方向分辨率,即每米有多少个像素,可以省略
        DWORD      biClrUsed;  //使用的颜色数,如果为0,则表示默认值(2^颜色位数)
        DWORD      biClrImportant; //重要颜色数,如果为0,则表示所有颜色都是重要的
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
这个结构体的信息量非常多,但需要我们填的其实并不多:
- biSize:指定此结构体的长度,一般都直接为:- sizeof(BITMAPINFOHEADER)即可
- biWidth:图片宽度,需要自己根据需要填
- biHeight:图片高度,需要自己根据需要填
- biPlanes:平面数,填1即可
- biBitCount:位数,本文采用的是24位图,所以填24
除了以上几个需要填的,其它都直接清零即可
由于第三个结构体只有低于24位的才用得到,所以这里不予讨论了
结构体为:
typedef struct tagRGBQUAD {
        BYTE    rgbBlue; //该颜色的蓝色分量
        BYTE    rgbGreen;  //该颜色的绿色分量
        BYTE    rgbRed; //该颜色的红色分量
        BYTE    rgbReserved;  //保留值
} RGBQUAD;
最后是数据区域,由于本文以24位图为主,所以每一个数据都是三个字节,分别代表红绿蓝
3 实战分析bmp图片数据
这里仍然是以24位图为例,用十六进制编辑器打开bmp图片:

首先来看第一个结构体:BITMAPFILEHEADER,其四个成员变量定义分别为:
- bfType:WORD,两个字节,这里为42 4D,右边也显示出字符为BM,但由于内存中数据排列高位在左,低位在右,所以代码中我们要写为0x4D42。
- bfSize:DWORD,4个字节,这里为0E 09 01 00,颠倒还原之后为 00 01 09 0E,换算为十进制为67854字节。
这和windows系统显示的结果保持一致:

然后是两个保留字段,没有意义,都为WORD类型,共占4个字节,跳过
- bfOffBits:DWORD,占4个字节,为36 00 00 00,颠倒还原后为00 00 00 36 ,换算为10进制为:54,两个结构体大小分别为:14 和 40 符合。

然后再来看第二个结构体:
- biSize:占4个字节,为28 00 00 00 ,还原后为00 00 00 28,换算为十进制为:40,符合该结构体的大小
- biWidth:4字节,为96 00 00 00,颠倒还原,转换为10进制为150
- biHeight:4字节,6A FF FF FF,颠倒后为 FF FF FF 6A,第一位为F(即1111),二进制首位为1,说明它为负数,根据补码规则转换为10进制后为 -150,可参考官方文档对它的说明:BITMAPINFOHEADER

简单来说,就是该数如果为正,那么该图片的数据就是倒着放的,先放倒数第一行的数据,然后是倒数第二行,最后放第一行。
而如果为负数,则相反,数据正着放,更符合我们的直觉。
同样符合:

- biPlanes:2字节,为01 00 ,还原后就是1,即一个平面数,符合
- biBitCount:2字节,为18 00,还原后为24,即24位图,符合
- biCompression:4字节,为00 00 00 00 ,即为0,未压缩,符合
- biSizeImage:4字节,同样是00 00 00 00,由于未压缩,所以该字段为0,符合
最后四个字段,都为4字节,即16个字节。都为0,即默认值,符合。

结束了前面两个结构体的解析,后面就是真正的颜色数据了,但由于这是一张二维码图片,只有黑白两色,所以里面的值就只有两个,分别为000000与FFFFFF,随便举个例子:

分别三个字节,即24位表示一个颜色,00 00 00 代表一个黑色点,而 FF FF FF则代表一个白色点。
