位图文件由三部分组成:文件头 + 位图信息 + 位图像素数据,具体的结构如下图所示:
1、位图文件头。位图文件头主要用于识别位图文件。以下是位图文件头结构的定义:
typedef struct tagBITMAPFILEHEADER { // bmfh
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
其中的bfType值应该是”BM”(0x4d42),标志该文件是位图文件。bfSize的值是位图文件的大小;bfOffBits是指从BITMAPFILEHEADER开始,直到位图像素数据存储点的内存大小(距离),即用bfOffBits – sizeof(BITMAPFILEHEADER)就能得到BITMAPINFO在位图中实际所占有的空间大小。
2、位图信息中所记录的值用于分配内存,设置调色板信息,读取像素值等。
以下是位图信息结构的定义:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO;
可见位图信息也是由两部分组成的:位图信息头 + 颜色表。
2.1 位图信息头
位图信息头包含了单个像素所用字节数以及描述颜色的格式,此外还包括位图的宽度、高度、目标设备的位平面数、图像的压缩格式。以下是位图信息头结构的定义:
typedef struct tagBITMAPINFOHEADER{ // bmih
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
2.2 颜色表
颜色表一般是针对16位以下的图像而设置的,对于16位和16位以上的图像,由于其位图像素数据中直接对对应像素的RGB(A)颜色进行描述,因而省却了调色板。而对于16位以下的图像,由于其位图像素数据中记录的只是调色板索引值,因而需要根据这个索引到调色板去取得相应的RGB(A)颜色。颜色表的作用就是创建调色板。
颜色表是由颜色表项组成的,颜色表项结构的定义如下:
typedef struct tagRGBQUAD { // rgbq
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
其中需要注意的问题是,RGBQUAD结构中的颜色顺序是BGR,而不是平常的RGB。
3、位图数据
最后,在位图文件头、位图信息头、位图颜色表之后,便是位图的主体部分:位图数据。根据不同的位图,位图数据所占据的字节数也是不同的:对于8位位图,每个字节代表了一个像素;对于16位位图,每两个字节代表了一个像素;对于24位位图,每三个字节代表了一个像素;对于32位位图,每四个字节代表了一个像素;而对于单色位图来说,每一位代表一个像素。
这里有两点特别需要强调的:
1) 位图数据的字节数组是从图像的最下面一行开始逐行向上存储的,所以在选取源位图的实际范围的时候需要特别注意!
我习惯先用一个函数,把位图数据读成一个和位图结构、方向相似的矩阵,即最先读出的位图数据,是矩阵的最后一行。
2) 存取位图数据的字节数组有个问题需要引起开发人员的注意:字节数组中每个扫描行的字节数必需是4的倍数(即是32位的倍数),如果不足要用0补齐。
举例说,我有一个20 * 20个点的单色位图。在保存位图的时候,第一行有20个点,虽然只需要用20位的数据来保存就可以了;但是,这个时候,不足32位,则需要用12位0来补足。
这样,总共需要 20 * (20 + 12) = 640 位来保存这一行,即需要用640/8 = 80个字节来保存整幅图像。