BMP是bitmap的缩写形式,bitmap顾名思义,就是位图也即Windows位图。它一般由4部分组成:文件头信息块、图像描述信息块、颜色表(在真彩色模式无颜色表)和图像数据区组成。在系统中以BMP为扩展名保存。
打开Windows的画图程序,在保存图像时,可以看到三个选项:2色位图(黑白)、16色位图、256色位图和24位位图。这是最普通的生成位图的工具,在这里讲解的BMP位图形式,主要就是指用画图生成的位图.
一般的bmp图像都是24位,也就是真彩。每8位为一字节,24位也就是使用三字节来存储每一个像素的信息,三个字节对应存放r,g,b三原色的数据每个字节的存贮范围都是0-255。那么以此类推,32位图即每像素存储r,g,b,a(Alpha通道,存储透明度)四种数据。8位图就是只有灰度这一种信息,还有二值图,它只有两种颜色,黑或者白。
现在讲解BMP的4个组成部分:
文件信息头 (14字节)存储着文件类型,文件大小等信息
// 文件信息头结构体
typedef struct tagBITMAPFILEHEADER
{
/unsigned short bfType; // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件
unsigned int bfSize; // 文件大小 以字节为单位(2-5字节)
unsigned short bfReserved1; // 保留,必须设置为0 (6-7字节)
unsigned short bfReserved2; // 保留,必须设置为0 (8-9字节)
unsigned int bfOffBits; // 从文件头到像素数据的偏移 (10-13字节)
} BITMAPFILEHEADER;
图片信息头 (40字节)存储着图像的尺寸,颜色索引,位平面数等信息
//图像信息头结构体
typedef struct tagBITMAPINFOHEADER
{
unsigned int biSize; // 此结构体的大小 (14-17字节)
long biWidth; // 图像的宽 (18-21字节)
long biHeight; // 图像的高 (22-25字节)
unsigned short biPlanes; // 表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1 (26-27字节)
unsigned short biBitCount; // 一像素所占的位数,一般为24 (28-29字节)
unsigned int biCompression; // 说明图象数据压缩的类型,0为不压缩。 (30-33字节)
unsigned int biSizeImage; // 像素数据所占大小, 这个值应该等于上面文件头结构中bfSize-bfOffBits (34-37字节)
long biXPelsPerMeter; // 说明水平分辨率,用象素/米表示。一般为0 (38-41字节)
long biYPelsPerMeter; // 说明垂直分辨率,用象素/米表示。一般为0 (42-45字节)
unsigned int biClrUsed; // 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。 (46-49字节)
unsigned int biClrImportant; // 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。(50-53字节)
} BITMAPINFOHEADER;
调色板 (由颜色索引数决定)【可以没有此信息】
//24位图像素信息结构体,即调色板
typedef struct _PixelInfo {
unsigned char rgbBlue; //该颜色的蓝色分量 (值范围为0-255)
unsigned char rgbGreen; //该颜色的绿色分量 (值范围为0-255)
unsigned char rgbRed; //该颜色的红色分量 (值范围为0-255)
unsigned char rgbReserved;// 保留,必须为0
} PixelInfo;
位图数据 (由图像尺寸决定)每一个像素的信息在这里存储
颜色表接下来位为位图文件的图像数据区,在此部分记录着每点像素对应的颜色号,其记录方式也随颜色模式而定,既2色图像每点占1位(8位为1字节);16色图像每点占4位(半字节);256色图像每点占8位(1字节);真彩色图像每点占24位(3字节)。所以,整个数据区的大小也会随之变化。究其规律而言,可的出如下计算公式:图像数据信息大小=(图像宽度图像高度记录像素的位数)/8。
然而,未压缩的图像信息区的大小。除了真彩色模式外,其余的均大于或等于数据信息的大小。这是为什么呢?原因有两个:
1.BMP文件记录一行图像是以字节为单位的。因此,就不存在一个字节中的数据位信息表示的点在不同的两行中。也就是说,设显示模式位16色,在每个字节分配两个点信息时,如果图像的宽度位奇数,那么最后一个像素点的信息将独占一个字节,这个字节的后4位将没有意义。接下来的一个字节将开始记录下一行的信息。
2.为了显示的方便,除了真彩色外,其他的每中颜色模式的行字节数要用数据“00”补齐为4的整数倍。如果显示模式为16色,当图像宽为19时,存储时每行则要补充4-(19/2+1)%4=2个字节(加1是因为里面有一个像素点要独占了一字节)。如果显示模式为256色,当图像宽为19时,每行也要补充4-19%4=1个字节。
还有一点我要申明,当屏幕初始化为16或256色模式时,一定要设置调色板或修正颜色值,否则无法得到正确的图像颜色。
说的太抽象,我们现在打开一张bmp文件,来看一看。
记住图片的信息:像素是502x179
大小是263KB
所占269986个字节
我们用ULtraEdit
打开bmp文件。显示的是16进制的代码。
现在我们来读取这些代码,看看他们到底保存了一些啥东西。
在这里要注意的是
Windows的数据是倒着念的,这是PC电脑的特色。如果一段数据为42 4D,倒着念就是4D 42,即0x4D42。
因此,如果bfSize的数据为A2 1E 04 00,实际上就成了0x00041EA2,也就是0x41EA2。
unsigned short bfType = 0x4D42 = 19778
unsigned int bfSize = 0x41EA2 = 269986字节=269986/1024=263kb
unsigned short bfReserved1 = 00 00
unsigned short bfReserved2 = 00 00
unsigned int bfOffBits = 0X00000036 = 0x36 = 54字节
unsigned int biSize = 0x00000028 = 0x28 = 40字节
int biWidth = 0x0001f6 = 0x1f6 = 502像素;
int biHeight = 0x000000B3 = 0xB3 = 179像素 ;
unsigned short biPlanes = 0x0001 0x1 = 1;
unsigned short biBitCount = 0x0018 = 0x18 = 24位;
unsigned int biCompression = 0x00000000 = 0;
unsigned int biSizeImage = 0x00000000 = 0;
int biXPelsPerMeter = 0x00001273 = 0x1273 = 4723;
int biYPelsPerMeter = 0x00001273 = 0x1273 = 4723;
unsigned int biClrUsed = 0x00000000 = 0;
unsigned int biClrImportant = 0x00000000 = 0;
unsigned char rgbBlue = 0x496864 其中R:0X64 G:0X68 B:0X49 ; //该颜色的蓝色分量 (值范围为0-255)
unsigned char rgbGreen = 0x4A6965 其中R:0X65 G:0X69 B:0X4A ; //该颜色的绿色分量 (值范围为0-255)
unsigned char rgbRed = 0x496663 其中R:0X63 G:0X66 B:0X49 ; //该颜色的红色分量 (值范围为0-255)
unsigned char rgbReserved; // 保留,必须为0
接下就是要用C语言来读取bmp文件,来具体看一下我们从二进制文本中读到的信息是否和调试的一样。
# ifndef BMP_H
# define BMP_H
/*
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; // 文件大小 以字节为单位(2-5字节)
unsigned short bfReserved1; // 保留,必须设置为0 (6-7字节)
unsigned short bfReserved2; // 保留,必须设置为0 (8-9字节)
unsigned int bfOffBits; // 从文件头到像素数据的偏移 (10-13字节)
} BITMAPFILEHEADER;
//图像信息头结构体
typedef struct tagBITMAPINFOHEADER
{
unsigned int biSize; // 此结构体的大小 (14-17字节)
long biWidth; // 图像的宽 (18-21字节)
long biHeight; // 图像的高 (22-25字节)
unsigned short biPlanes; // 表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1 (26-27字节)
unsigned short biBitCount; // 一像素所占的位数,一般为24 (28-29字节)
unsigned int biCompression; // 说明图象数据压缩的类型,0为不压缩。 (30-33字节)
unsigned int biSizeImage; // 像素数据所占大小, 这个值应该等于上面文件头结构中bfSize-bfOffBits (34-37字节)
long biXPelsPerMeter; // 说明水平分辨率,用象素/米表示。一般为0 (38-41字节)
long biYPelsPerMeter; // 说明垂直分辨率,用象素/米表示。一般为0 (42-45字节)
unsigned int biClrUsed; // 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。 (46-49字节)
unsigned int biClrImportant; // 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。(50-53字节)
} BITMAPINFOHEADER;
//24位图像素信息结构体,即调色板
typedef struct _PixelInfo {
unsigned char rgbBlue; //该颜色的蓝色分量 (值范围为0-255)
unsigned char rgbGreen; //该颜色的绿色分量 (值范围为0-255)
unsigned char rgbRed; //该颜色的红色分量 (值范围为0-255)
unsigned char rgbReserved;// 保留,必须为0
} PixelInfo;
#endif
#include
#include
#include "BmpFormat.h"
BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;
void showBmpHead(BITMAPFILEHEADER pBmpHead)
{ //定义显示信息的函数,传入文件头结构体
printf("BMP文件大小:%dkb\n", fileHeader.bfSize/1024);
printf("保留字必须为0:%d\n", fileHeader.bfReserved1);
printf("保留字必须为0:%d\n", fileHeader.bfReserved2);
printf("实际位图数据的偏移字节数: %d\n", fileHeader.bfOffBits);
}
void showBmpInfoHead(BITMAPINFOHEADER pBmpinfoHead)
{//定义显示信息的函数,传入的是信息头结构体
printf("位图信息头:\n" );
printf("信息头的大小:%d\n" ,infoHeader.biSize);
printf("位图宽度:%d\n" ,infoHeader.biWidth);
printf("位图高度:%d\n" ,infoHeader.biHeight);
printf("图像的位面数(位面数是调色板的数量,默认为1个调色板):%d\n" ,infoHeader.biPlanes);
printf("每个像素的位数:%d\n" ,infoHeader.biBitCount);
printf("压缩方式:%d\n" ,infoHeader.biCompression);
printf("图像的大小:%d\n" ,infoHeader.biSizeImage);
printf("水平方向分辨率:%d\n" ,infoHeader.biXPelsPerMeter);
printf("垂直方向分辨率:%d\n" ,infoHeader.biYPelsPerMeter);
printf("使用的颜色数:%d\n" ,infoHeader.biClrUsed);
printf("重要颜色数:%d\n" ,infoHeader.biClrImportant);
}
int main()
{
FILE* fp;
fp = fopen("1.bmp", "rb");//读取同目录下的image.bmp文件。
if(fp == NULL)
{
printf("打开'image.bmp'失败!\n");
return -1;
}
//如果不先读取bifType,根据C语言结构体Sizeof运算规则——整体大于部分之和,从而导致读文件错位
unsigned short fileType;
fread(&fileType,1,sizeof (unsigned short), fp);
if (fileType = 0x4d42)
{
printf("文件类型标识正确!" );
printf("\n文件标识符:%d\n", fileType);
fread(&fileHeader, 1, sizeof(BITMAPFILEHEADER), fp);
showBmpHead(fileHeader);
fread(&infoHeader, 1, sizeof(BITMAPINFOHEADER), fp);
showBmpInfoHead(infoHeader);
fclose(fp);
}
}
记住bmp文件的文件名是1.bmp
,这是我的文件名。当然你可以随便设置。而且要把图片和你的代码文件放到同一文件夹。
接下来就进行代码调试。
我这里用的是vscode调试的代码的。其他的编译器也可以,如果你也想安装vscode请看:10分钟搭建VScode的C/C++开发环境
将代码编写完成后就可以按F5
,进行调试。
可以看到这些信息都是正确的,说明我们的程序是没有错误的,至此我们的通过C语言来实现bmp文件的读取就完成了,接下来我们就要实现bmp文件的读取和保存,具体看我的其他文章。
BMP 文件通常是不压缩的,所以它们通常比同一幅图像的压缩图像文件格式要大很多。例如,一个 800×600 的 24位几乎占据 1.4MB 空间。因此它们通常不适合在因特网或者其它低速或者有容量限制的媒介上进行传输。 根据颜色深度的不同,图像上的一个像素可以用一个或者多个字节表示,它由 n/8 所确定(n 是位深度,1 字节包含 8 个数据位)。图片浏览器等基于字节的 ASCII 值计算像素的颜色,然后从调色板中读出相应的值。更为详细的信息请参阅下面关于位图文件的部分。 N 位 2n 种颜色的位图近似字节数可以用下面的公式计算: BMP 文件大小约等于 54+42 的 n 次方+(wh*n)/8,其中高度和宽度都是像素数。 需要注意的是上面公式中的 54 是位图文件的文件头,是彩色调色板的大小。另外需要注意的是这是一个近似值,对于 n 位的位图图像来说,尽管可能有最多 2n 中颜色,一个特定的图像可能并不会使用这些所有的颜色。由于彩色调色板仅仅定义了图像所用的颜色,所以实际的彩色调色板将小于。 如果想知道这些值是如何得到的,请参考下面文件格式的部分。 由于存储算法本身决定的因素,根据几个图像参数的不同计算出的大小与实际的文件大小将会有一些细小的差别。