简介:
BMP是一种与硬件设备无关的图像文件格式,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的
空间很大。BMP文件的图像深度可选lbit、4bit、8bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。
由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。
文件结构:
典型的BMP图像文件由四部分组成:
1:位图文件头数据结构,它包含BMP图像文件的类型、显示内容等信息;
2:位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
3:调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
4:位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。
位图的类型:
位图一共有两种类型,即:设备相关位图(DDB)和设备无关位图(DIB)。DDB位图在早期
的Windows系统(Windows
3.0以前)中是很普遍的,事实上它也是唯一的。然而,随着显示器制造技术的进步,以及显示设备的多样化,DDB位图的一些固有的问题开始浮现出来了。比
如,它不能够存储(或者说获取)创建这张图片的原始设备的分辨率,这样,应用程序就不能快速的判断客户机的显示设备是否适合显示这张图片。为了解决这一难
题,微软创建了DIB位图格式。
设备无关位图 (Device-Independent Bitmap)
DIB位图包含下列的颜色和尺寸信息:
* 原始设备(即创建图片的设备)的颜色格式。
* 原始设备的分辨率。
* 原始设备的调色板
* 一个位数组,由红、绿、蓝(RGB)三个值代表一个像素。
* 一个数组压缩标志,用于表明数据的压缩方案(如果需要的话)。
以上这些信息保存在BITMAPINFO结构中,该结构由BITMAPINFOHEADER结
构和两个或更多个RGBQUAD结构所组成。BITMAPINFOHEADER结构所包含的成员表明了图像的尺寸、原始设备的颜色格式、以及数据压缩方案
等信息。RGBQUAD结构标识了像素所用到的颜色数据。
DIB位图也有两种形式,即:底到上型DIB(bottom-up),和顶到下型
DIB(top-down)。底到上型DIB的原点(origin)在图像的左下角,而顶到下型DIB的原点在图像的左上角。如果DIB的高度值(由
BITMAPINFOHEADER结构中的biHeight成员标识)是一个正值,那么就表明这个DIB是一个底到上型DIB,如果高度值是一个负值,那
么它就是一个顶到下型DIB。注意:顶到下型的DIB位图是不能被压缩的。
位图的颜色格式是通过颜色面板值(planes)和颜色位值(bitcount)计算得来的,
颜色面板值永远是1,而颜色位值则可以是1、4、8、16、24、32其中的一个。如果它是1,则表示位图是一张单色位图(译者注:通常是黑白位图,只有
黑和白两种颜色,当然它也可以是任意两种指定的颜色),如果它是4,则表示这是一张VGA位图,如果它是8、16、24、或是32,则表示该位图是其他设
备所产生的位图。如果应用程序想获取当前显示设备(或打印机)的颜色位值(或称位深度),可调用API函数GetDeviceCaps(),并将第二个参
数设为BITSPIXEL即可。
对应的数据结构:
1:BMP文件组成
BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。
2:BMP文件头(14字节)
BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。
其结构定义如下:
typedef struct tagBITMAPFILEHEADER
{
WORDbf Type; // 位图文件的类型,必须为BMP(0-1字节)
DWORD bfSize; // 位图文件的大小,以字节为单位(2-5字节)
WORD bfReserved1; // 位图文件保留字,必须为0(6-7字节)
WORD bfReserved2; // 位图文件保留字,必须为0(8-9字节)
DWORD bfOffBits; // 位图数据的起始位置,以相对于位图(10-13字节)
// 文件头的偏移量表示,以字节为单位
} BITMAPFILEHEADER;
3:位图信息头(40字节)
BMP位图信息头数据用于说明位图的尺寸等信息。
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; // 本结构所占用字节数(14-17字节)
LONG biWidth; // 位图的宽度,以像素为单位(18-21字节)
LONG biHeight; // 位图的高度,以像素为单位(22-25字节)
WORD biPlanes; // 目标设备的级别,必须为1(26-27字节)
WORD biBitCount;// 每个像素所需的位数,必须是1(双色),(28-29字节)
// 4(16色),8(256色)或24(真彩色)之一
DWORD biCompression; // 位图压缩类型,必须是 0(不压缩),(30-33字节)
// 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
DWORD biSizeImage; // 位图的大小,以字节为单位(34-37字节)
LONG biXPelsPerMeter; // 位图水平分辨率,每米像素数(38-41字节)
LONG biYPelsPerMeter; // 位图垂直分辨率,每米像素数(42-45字节)
DWORD biClrUsed;// 位图实际使用的颜色表中的颜色数(46-49字节)
DWORD biClrImportant;// 位图显示过程中重要的颜色数(50-53字节)
} BITMAPINFOHEADER;
4:颜色表
颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:
typedef struct tagRGBQUAD {
BYTE rgbBlue;// 蓝色的亮度(值范围为0-255)
BYTE rgbGreen; // 绿色的亮度(值范围为0-255)
BYTE rgbRed; // 红色的亮度(值范围为0-255)
BYTE rgbReserved;// 保留,必须为0
} RGBQUAD;
颜色表中RGBQUAD结构数据的个数有biBitCount来确定:
当biBitCount=1,4,8时,分别有2,16,256个表项;
当biBitCount=24时,没有颜色表项。
位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader; // 位图信息头
RGBQUAD bmiColors[1]; // 颜色表
} BITMAPINFO;
5:位图数据
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:
当biBitCount=1时,8个像素占1个字节;
当biBitCount=4时,2个像素占1个字节;
当biBitCount=8时,1个像素占1个字节;
当biBitCount=24时,1个像素占3个字节;
Windows规定一个扫描行所占的字节数必须是
4的倍数(即以long为单位),不足的以0填充,
biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight;
具体数据举例:
如某BMP文件开头:
4D42 4690 0000 0000 0000 4600 0000 2800
0000 8000 0000 9000 0000 0100*1000 0300 0000 0090 0000 A00F 0000 A00F
0000 0000 0000 0000 0000*00F8 0000 E007 0000 1F00 0000 0000 0000*02F1
84F1 04F1 84F1 84F1 06F2 84F1 06F2 04F2 86F2 06F2 86F2 86F2 .... ....
BMP文件可分为四个部分:位图文件头、位图信息头、彩色板、图像数据阵列,在上图中已用*分隔。
一、图像文件头
1)1:(这里的数字代表的是"字",即两个字节,下同)图像文件头。0x4D42=’BM’,表示是Windows支持的BMP格式。
2)2-3:整个文件大小。4690 0000,为00009046h=36934。
3)4-5:保留,必须设置为0。
4)6-7:从文件开始到位图数据之间的偏移量。4600 0000,为00000046h=70,上面的文件头就是35字=70字节。
二、位图信息头
5)8-9:位图图信息头长度。
6)10-11:位图宽度,以像素为单位。8000 0000,为00000080h=128。
7)12-13:位图高度,以像素为单位。9000 0000,为00000090h=144。
8)14:位图的位面数,该值总是1。0100,为0001h=1。
9)15:每个像素的位数。有1(单色),4(16色),8(256色),16(64K色,高彩色),24(16M色,真彩色),32(4096M色,增强型真彩色)。1000为0010h=16。
10)16-17:压缩说明:有0(不压缩),1(RLE
8,8位RLE压缩),2(RLE
4,4位RLE压缩,3(Bitfields,位域存放)。RLE简单地说是采用像素数+像素值的方式进行压缩。T408采用的是位域存放方式,用两个字
节表示一个像素,位域分配为r5b6g5。图中0300 0000为00000003h=3。
11)18-19:用字节数表示的位图数据的大小,该数必须是4的倍数,数值上等于(≥位图宽度的最小的4的倍数)×位图高度×每个像素位数。0090 0000为00009000h=80×90×2h=36864。
12)20-21:用象素/米表示的水平分辨率。A00F 0000为0000 0FA0h=4000。
13)22-23:用象素/米表示的垂直分辨率。A00F 0000为0000 0FA0h=4000。
14)24-25:位图使用的颜色索引数。设为0的话,则说明使用所有调色板项。
15)26-27:对图象显示有重要影响的颜色索引的数目。如果是0,表示都重要。
三、彩色板
16)28-....(不确定):彩色板规范。对于调色板中的每个表项,用下述方法来描述RGB的值:
1字节用于蓝色分量
1字节用于绿色分量
1字节用于红色分量
1字节用于填充符(设置为0)
对于24-位真彩色图像就不使用彩色板,因为位图中的RGB值就代表了每个象素的颜色。
如,彩色板为00F8 0000 E007 0000 1F00 0000 0000 0000,其中:
00FB 0000为FB00h=1111100000000000(二进制),是蓝色分量的掩码。
E007 0000为 07E0h=0000011111100000(二进制),是绿色分量的掩码。
1F00 0000为001Fh=0000000000011111(二进制),是红色分量的掩码。
0000 0000总设置为0。
将掩码跟像素值进行“与”运算再进行移位操作就可以得到各色分量值。看看掩码,就可以明白事实
上在每个像素值的两个字节16位中,按从高到低取5、6、5位分别就是r、g、b分量值。取出分量值后把r、g、b值分别乘以8、4、8就可以补齐第个分
量为一个字节,再把这三个字节按rgb组合,放入存储器(同样要反序),就可以转换为24位标准BMP格式了。
四、图像数据阵列
17)27(无调色板)-...:每两个字节表示一个像素。阵列中的第一个字节表示位图左下角的象素,而最后一个字节表示位图右上角的象素。
五、存储算法
BMP文件通常是不压缩的,所以它们通常比同一幅图像的压缩图像文件格式要大很多。例如,一个
800×600的24位几乎占据1.4MB空间。因此它们通常不适合在因特网或者其它低速或者有容量限制的媒介上进行传输。
根据颜色深度的不同,图像上的一个像素可以用一个或者多个字节表示,它由n/8所确定(n是位深度,1字节包含8个数据位)。图片浏览器等基于字节的
ASCII值计算像素的颜色,然后从调色板中读出相应的值。更为详细的信息请参阅下面关于位图文件的部分。
n位2n种颜色的位图近似字节数可以用下面的公式计算: BMP文件大小约等于 54+4*2的n次方+(w*h*n)/8
,其中高度和宽度都是像素数。
需要注意的是上面公式中的54是位图文件的文件头,是彩色调色板的大小。另外需要注意的是这是一个近似值,对于n位的位图图像来说,尽管可能有最多2n中
颜色,一个特定的图像可能并不会使用这些所有的颜色。由于彩色调色板仅仅定义了图像所用的颜色,所以实际的彩色调色板将小于。
如果想知道这些值是如何得到的,请参考下面文件格式的部分。
由于存储算法本身决定的因素,根据几个图像参数的不同计算出的大小与实际的文件大小将会有一些细小的差别。
C++写BMP位图:
主流USB视频摄像头的媒体格式为RGB24和I420,如何把这些数据变成BMP位图存下来呢?请看本文。
首先,我们了解一下BMP的格式,BMP有四部分组成,用表格表示如下:
|
|
|
|
1. 文件信息头 |
2. 位图信息头 |
3. 调色板 |
4. 位图数据 |
第一部分,文件信息头的格式如下:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
共有14个字节,其中bfType为文件类型,BMP的类型为0×4d42,也就是字母m和b;bfSize是文件大小,为1,2,3,4部分大小的总和;bfReserved1和bfReserved2为1,2,3部分大小的总和。
第二部分,位图信息头,定义如下:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
总共40个字节,字段比较多,可以查看MSDN中的说明,对于RGB24的位图,这个结构体一般定义如下:
BITMAPINFOHEADER bih;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = width;
bih.biHeight = height;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = 0;
bih.biSizeImage = size;
bih.biXPelsPerMeter = 0;
bih.biYPelsPerMeter = 0;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
第三部分是调色板信息,定义如下:
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
这部分用来表示RGB各色的强度,一般情况我们不把这一部分写到文件中。
第四部分就是真正的数据,比如宽度和高度分别是320和240,这部分数据的长度应该为320*240*3(每个像素点上有3个字节,分别用来表示b,g,r的颜色)。
根据对BMP格式的说明,我们可以轻易的写出一个生成BMP图像的函数,如下所示:
// pData为rgb24数据,width为位图宽度,height为位图高度,filename为位图文件的名字
void Snapshot( BYTE * pData, int width, int height, const char * filename )
{
int size = width*height*3; // 每个像素点3个字节
// 位图第一部分,文件信息
BITMAPFILEHEADER bfh;
bfh.bfType = 0×4d42; //bm
bfh.bfSize = size // data size
+ sizeof( BITMAPFILEHEADER ) // first section size
+ sizeof( BITMAPINFOHEADER ) // second section size
;
bfh.bfReserved1 = 0; // reserved
bfh.bfReserved2 = 0; // reserved
bfh.bfOffBits = bfh.bfSize - size;
// 位图第二部分,数据信息
BITMAPINFOHEADER bih;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = width;
bih.biHeight = height;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = 0;
bih.biSizeImage = size;
bih.biXPelsPerMeter = 0;
bih.biYPelsPerMeter = 0;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
FILE * fp = fopen( filename, “wb” );
if( !fp ) return;
fwrite( &bfh, 1, sizeof(BITMAPFILEHEADER), fp );
fwrite( &bih, 1, sizeof(BITMAPINFOHEADER), fp );
fwrite( pData, 1, size, fp );
fclose( fp );
}
如果要对摄像头采集到的数据进行截图,这里可以直接把数据送给这个函数,就可以生成一张位图了。当然也可以自己生成一段数据,用图片的形式保存下来。下面我将给一个例子,说明怎么生成位图,本例中说明了如何生成一个320*240的图像,黑色的背景,中部有一个100*100的矩形,代码如下:
void GenerateBMP()
{
int i=0, j=0;
struct {
BYTE b;
BYTE g;
BYTE r;
} pRGB[240][320]; // 定义位图数据
memset( pRGB, 0, sizeof(pRGB) ); // 设置背景为黑色
// 在中间画一个100*100的矩形
for( int i=70;i<170;i++ ){
for( int j=110;j<210;j++ ){
pRGB[i][j].r = 0xff;
}
}
// 生成BMP图片
Snapshot( (BYTE *)pRGB, 320, 240, “C://rgb.bmp” );
}
生成图片如下所示:
上面所说的都是对于RGB24的数据,对于I420,可以先转换成RGB24,然后在生成位图。
BMP的数据量是很大的,没有经过任何的压缩,我们可以计算一下,对于一副320*240的图像,图片的大小是320*240*3 = 230400 bytes = 225 Kbytes,普通电脑一副屏幕截图就要1024*768*3=2.3M。所以我们要用一些流行的算法对图片进行压缩才便于存储,下一篇文章,我将介绍如何把BMP的文件压缩成JPG的文件,压缩比大概在80倍左右,请关注下文。
11-10补充:
感谢张欣网友把上述代码完整的整理出来,经过我的小的改动,C代码如下:
#include “stdio.h”
#include “stdlib.h”
#include “string.h”
typedef long BOOL;
typedef long LONG;
typedef unsigned char BYTE;
typedef unsigned long DWORD;
typedef unsigned short WORD;
#include <pshpack2.h>
typedef struct {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BMPFILEHEADER_T;
struct BMPFILEHEADER_S{
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
};
typedef struct{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BMPINFOHEADER_T;
void Snapshot( BYTE * pData, int width, int height, char * filename )
{
int size = width*height*3; // 每个像素点3个字节
// 位图第一部分,文件信息
BMPFILEHEADER_T bfh;
bfh.bfType = 0×4d42; //bm
bfh.bfSize = size // data size
+ sizeof( BMPFILEHEADER_T ) // first section size
+ sizeof( BMPINFOHEADER_T ) // second section size
;
bfh.bfReserved1 = 0; // reserved
bfh.bfReserved2 = 0; // reserved
bfh.bfOffBits = bfh.bfSize - size;
// 位图第二部分,数据信息
BMPINFOHEADER_T bih;
bih.biSize = sizeof(BMPINFOHEADER_T);
bih.biWidth = width;
bih.biHeight = height;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = 0;
bih.biSizeImage = size;
bih.biXPelsPerMeter = 0;
bih.biYPelsPerMeter = 0;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
FILE * fp = fopen( filename,”wb” );
if( !fp ) return;
fwrite( &bfh, 1, sizeof(BMPFILEHEADER_T), fp );
fwrite( &bih, 1, sizeof(BMPINFOHEADER_T), fp );
fwrite( pData, 1, size, fp );
fclose( fp );
}
void main()
{
int i=0, j=0;
struct {
BYTE b;
BYTE g;
BYTE r;
} pRGB[240][320]; // 定义位图数据
memset( pRGB, 0, sizeof(pRGB) ); // 设置背景为黑色
// 在中间画一个100*100的矩形
for( i=70;i<170;i++ ){
for( j=110;j<210;j++ ){
pRGB[i][j].r = 0xff;
}
}
// 生成BMP图片
Snapshot( ( BYTE*)pRGB, 320, 240, “c://rgb.bmp” );
}