心血来潮想了解下常用图片的格式解析,翻看了一些资料后,发现最简单的是bmp格式,所以先拿它开刀。
BMP格式
这种格式内的数据分为三到四个部分,依次是:
- 文件信息头 (14字节)存储着文件类型,文件大小等信息
- 图片信息头 (40字节)存储着图像的尺寸,颜色索引,位平面数等信息
- 调色板 (由颜色索引数决定)【可以没有此信息】
- 位图数据 (由图像尺寸决定)每一个像素的信息在这里存储
一般的bmp图像都是24位,也就是真彩。每8位为一字节,24位也就是使用三字节来存储每一个像素的信息,三个字节对应存放r,g,b三原色的数据,每个字节的存贮范围都是0-255。
那么以此类推,32位图即每像素存储r,g,b,a(Alpha通道,存储透明度)四种数据。8位图就是只有灰度这一种信息,还有二值图,它只有两种颜色,黑或者白。
文件信息头格式
typedef struct tagBITMAPFILEHEADER {
unsigned short bfType; // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件
unsigned int bfSize; // 文件大小
unsigned short bfReserved1; // 保留,必须设置为0
unsigned short bfReserved2; // 保留,必须设置为0
unsigned int bfOffBits; // 从文件头到像素数据的偏移
} BITMAPFILEHEADER;
图片信息头格式
typedef struct tagBITMAPINFOHEADER {
unsigned int biSize; // 此结构体的大小
int biWidth; // 图像的宽
int biHeight; // 图像的高
unsigned short biPlanes; // 表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1
unsigned short biBitCount; // 一像素所占的位数,一般为24
unsigned int biCompression; // 说明图象数据压缩的类型,0为不压缩。
unsigned int biSizeImage; // 像素数据所占大小, 这个值应该等于上面文件头结构中bfSize-bfOffBits
int biXPelsPerMeter; // 说明水平分辨率,用象素/米表示。一般为0
int biYPelsPerMeter; // 说明垂直分辨率,用象素/米表示。一般为0
unsigned int biClrUsed; // 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。
unsigned int biClrImportant;// 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。
} BITMAPINFOHEADER;
调色板信息
这里需要根据文件信息头的bfOffBits是否等于54(由前面的固定14+40字节得出)来判断是否存在此调色板信息,如果是,则不存在;大于的话即存在。
可以根据需求提取其中的信息,或者直接移动到位图数据区读取像素信息。
这个地方可以表示为一个二维数组unsigned char palette[N][M], 其中N表示总的颜色索引数,M表示每像素占的字节数。例如一个24位图,每像素由3个字节构成,M即为3,每个字节可表示0-255共256种颜色,所以N为256 。
数组中存放的是索引信息,也就是一张映射表,标识颜色索引号与其代表的颜色的对应关系
位图数据
这里就存放着所有的像素信息了,每像素为一字节,读取出来后通过查询调色板获得颜色信息。
如果图像是24位或是32位数据的位图的话,位图数据区就不是索引而是实际的像素值了。下面说明一下,此时位图数据区的每个像素的RGB颜色阵列排布:
24位RGB按照BGR的顺序来存储每个像素的各颜色通道的值,一个像素的所有颜色分量值都存完后才存下一个下一个像素,不进行交织存储。
32位数据按照BGRA的顺序存储,其余与24位位图的方式一样。
注意:由于位图信息头中的图像高度是正数,所以位图数据在文件中的排列顺序是从左下角到右上角,以行为主序排列的。
也就是说,最先读取到的是位于从上往下数最后一行最左端的像素,然后是同行向右一列的像素,读取完一整行后,继续读取倒数第二行,然后继续向上直到读完所有数据。
对齐规则
我们知道Windows默认的扫描的最小单位是4字节,如果数据对齐满足这个值的话对于数据的获取速度等都是有很大的增益的。因此,BMP图像顺应了这个要求,要求每行的数据的长度必须是4的倍数,如果不够需要进行比特填充(以0填充),这样可以达到按行的快速存取。这时,位图数据区的大小就未必是 图片宽×每像素字节数×图片高 能表示的了,因为每行可能还需要进行比特填充。
填充后的每行的字节数为:
int iLineByteCnt = (((m_iImageWidth * m_iBitsPerPixel) + 31) >> 5) << 2;
其中m_iBitsPerPixel为每像素的比特数。
这样,位图数据区的大小为:
m_iImageDataSize = iLineByteCnt * m_iImageHeight;
我们在扫描完一行数据后,也可能接下来的数据并不是下一行的数据,可能需要跳过一段填充数据:
skip = 4 - ((m_iImageWidth * m_iBitsPerPixel)>>3) & 3;
实现代码
以24位图为例
/* BmpFormat.h
* 存放上述几个结构体
*/
// 文件头结构体
typedef struct tagBITMAPFILEHEADER {
unsigned short bfType; // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件
unsigned int bfSize; // 文件大小
unsigned short bfReserved1; // 保留,必须设置为0
unsigned short bfReserved2; // 保留,必须设置为0
unsigned int bfOffBits; // 从文件头到像素数据的偏移
} BITMAPFILEHEADER;
//图像信息头结构体
typedef struct tagBITMAPINFOHEADER {
unsigned int biSize; // 此结构体的大小
int biWidth; // 图像的宽
int biHeight; // 图像的高
unsigned short biPlanes; // 表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1
unsigned short biBitCount; // 一像素所占的位数,一般为24
unsigned int biCompression; // 说明图象数据压缩的类型,0为不压缩。
unsigned int biSizeImage; // 像素数据所占大小, 这个值应该等于上面文件头结构中bfSize-bfOffBits
int biXPelsPerMeter; // 说明水平分辨率,用象素/米表示。一般为0
int biYPelsPerMeter; // 说明垂直分辨率,用象素/米表示。一般为0
unsigned int biClrUsed; // 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。
unsigned int biClrImportant;// 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。
} BITMAPINFOHEADER;
//24位图像素信息结构体
typedef struct _PixelInfo {
unsigned char b;
unsigned char g;
unsigned char r;
} PixelInfo;
/* TestBmp.c
*
*/
#include <stdio.h>
#include <malloc.h>
#include "BmpFormat.h"
int main(){
FILE* fp;
fp = fopen("image.bmp", "rb");//读取同目录下的image.bmp文件。
if(fp == NULL){
printf("打开'image.bmp'失败!
");
return -1;
}
BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;
//从文件里读取信息
fread(&fileHeader, sizeof(fileHeader), 1, fp);
fread(&infoHeader, sizeof(infoHeader), 1, fp);
//解析头信息
if(fileHeader.bfType != 19778){
printf("'image.bmp'不是一个bmp格式的文件。
");
return -1;
}
//输出文件信息
printf("大小:%dkb
", fileHeader.bfSize);
printf("尺寸:宽%d 高%d
", infoHeader.biWidth, infoHeader.biHeight);
printf("位数:%d
", infoHeader.biBitCount);
printf("偏移量:%d
", fileHeader.bfOffBits);
if(infoHeader.biBitCount != 24 || fileHeader.bfOffBits != 54){
printf("
暂时只支持24位图片。
");
return -1;
}
//计算补齐量(这里用更容易理解的除法和求余,效果和位运算相同)
int offset = (infoHeader.biBitCount*infoHeader.biWidth/8)%4;
if (offset != 0){
offset = 4 - offset;
}
//动态创建一块内存以存储像素信息
int size = infoHeader.biHeight * (infoHeader.biWidth + offset);
PixelInfo* pixel = (PixelInfo*)malloc(size);
//循环读取
for (i=0; i<infoHeader.biHeight; i++){
for (j=0; j<infoHeader.biWidth; j++){
fread(pixel, sizeof(PixelInfo), 1, fp);
}
if (offset != 0){
for (j=0; j<offset; j++){
fread(pixel, sizeof(unsigned char), 1, fp);
}
}
}
//可以在这里对像素进行处理
//比如输出左下角的像素信息
printf("
左下角的像素的RGB为:%d %d %d
", pixel[0].r, pixel[0].g, pixel[0].b);
//释放内存
free(pixel);
return 0;
}