天天看点

BMP文件结构及图像每行字节计算方法

BMP文件结构

1. BMP文件组成   BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。文件头主要包含文件的大小、文件类型、图像数据偏离文件头的长度等信息;位图信息头包含图象的尺寸信息、图像用几个比特数值来表示一个像素、图像是否压缩、图像所用的颜色数等信息。颜色信息包含图像所用到的颜色表,显示图像时需用到这个颜色表来生成调色板,但如果图像为真彩色,既图像的每个像素用24个比特来表示,文件中就没有这一块信息,也就不需要操作调色板。文件中的数据块表示图像的相应的像素值,需要注意的是:图像的像素值在文件中的存放顺序为从左到右,从下到上,也就是说,在BMP文件中首先存放的是图像的最后一行像素,最后才存储图像的第一行像素,但对与同一行的像素,则是按照先左边后右边的的顺序存储的;另外一个需要读者朋友关注的细节是:文件存储图像的每一行像素值时,如果存储该行像素值所占的字节数为4的倍数,则正常存储,否则,需要在后端补0,凑足4的倍数。   

2. BMP文件头   BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。其结构定义如下: typedef struct tagBITMAPFILEHEADER { WORD bfType; // 位图文件的类型,必须为“BM” DWORD bfSize; // 位图文件的大小,以字节为单位 WORD bfReserved1; // 位图文件保留字,必须为0 WORD bfReserved2; // 位图文件保留字,必须为0 DWORD bfOffBits; // 位图数据的起始位置,以相对于位图文件头的偏移量表示,以字节为单位 } BITMAPFILEHEADER;该结构占据14个字节。   

3. 位图信息头   BMP位图信息头数据用于说明位图的尺寸等信息。其结构如下: typedef struct tagBITMAPINFOHEADER{ DWORD biSize; // 本结构所占用字节数 LONG biWidth; // 位图的宽度,以像素为单位 LONG biHeight; // 位图的高度,以像素为单位 WORD biPlanes; // 目标设备的平面数不清,必须为1 WORD biBitCount// 每个像素所需的位数,必须是1(双色), 4(16色),8(256色)或24(真彩色)之一 DWORD biCompression; // 位图压缩类型,必须是 0(不压缩),1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一 DWORD biSizeImage; // 位图的大小,以字节为单位 LONG biXPelsPerMeter; // 位图水平分辨率,每米像素数 LONG biYPelsPerMeter; // 位图垂直分辨率,每米像素数 DWORD biClrUsed;// 位图实际使用的颜色表中的颜色数 DWORD biClrImportant;// 位图显示过程中重要的颜色数 } BITMAPINFOHEADER;该结构占据40个字节。   注意:对于BMP文件格式,在处理单色图像和真彩色图像的时候,无论图象数据多么庞大,都不对图象数据进行任何压缩处理,一般情况下,如果位图采用压缩格式,那么16色图像采用RLE4压缩算法,256色图像采用RLE8压缩算法。  

 4. 颜色表   颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下: typedef struct tagRGBQUAD { BYTErgbBlue;// 蓝色的亮度(值范围为0-255) BYTErgbGreen; // 绿色的亮度(值范围为0-255) BYTErgbRed; // 红色的亮度(值范围为0-255) BYTErgbReserved;// 保留,必须为0 } RGBQUAD;   颜色表中RGBQUAD结构数据的个数由BITMAPINFOHEADER 中的biBitCount项来确定,当biBitCount=1,4,8时,分别有2,16,256个颜色表项,当biBitCount=24时,图像为真彩色,图像中每个像素的颜色用三个字节表示,分别对应R、G、B值,图像文件没有颜色表项。位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下: typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; // 位图信息头 RGBQUAD bmiColors[1]; // 颜色表 } BITMAPINFO;   注意:RGBQUAD数据结构中,增加了一个保留字段rgbReserved,它不代表任何颜色,必须取固定的值为“0”,同时,RGBQUAD结构中定义的颜色值中,红色、绿色和蓝色的排列顺序与一般真彩色图像文件的颜色数据排列顺序恰好相反,既:若某个位图中的一个像素点的颜色的描述为“00,00,ff,00”,则表示该点为红色,而不是蓝色。  

 5. 位图数据   位图数据记录了位图的每一个像素值或该对应像素的颜色表的索引值,图像记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。这种格式我们又称为Bottom_Up位图,当然与之相对的还有Up_Down形式的位图,它的记录顺序是从上到下的,对于这种形式的位图,也不存在压缩形式。位图的一个像素值所占的字节数:当biBitCount=1时,8个像素占1个字节;当biBitCount=4时,2个像素占1个字节;当biBitCount=8时,1个像素占1个字节;当biBitCount=24时,1个像素占3个字节,此时图像为真彩色图像。当图像不是为真彩色时,图像文件中包含颜色表,位图的数据表示对应像素点在颜色表中相应的索引值,当为真彩色时,每一个像素用三个字节表示图像相应像素点彩色值,每个字节分别对应R、G、B分量的值,这时候图像文件中没有颜色表。上面我已经讲过了,Windows规定图像文件中一个扫描行所占的字节数必须是4的倍数(即以字为单位),不足的以0填充,图像文件中一个扫描行所占的字节数计算方法: DataSizePerLine= (biWidth* biBitCount+31)/8;// 一个扫描行所占的字节数或者 DataSizePerLine= (biWidth* biBitCount+31)/32 * 4;// 一个扫描行所占的字节数 (如果biBitCount == 8 或24) DataSizePerLine= (biWidth* 3+3)/4*4;// 一个扫描行所占的字节数或 DataSizePerLine= (biWidth*1+3)/4*4;// 一个扫描行所占的字节数  位图数据的大小按下式计算(不压缩情况下):   DataSize= DataSizePerLine* biHeight。  

 上述是BMP文件格式的说明,搞清楚了以上的结构,就可以正确的操作图像文件,对它进行读或写操作了。六、图像每行字节数的计算 利用宏:#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4) bmp文件的数据块部分不是直接的一个个像素排列后存储。为了要保证每行的字节数都能够被4整除,往往要在每行数据后面补充1,2或3个字节的冗余信息。 X*Y大小的24位bmp图像,每个像素占3个字节,如果X*3后不能够被4整除,则每行有可能是X*3+1, X*3+2或X*3+3, 取决于哪个数值可以被4整除。 这里有一个宏,可以很方便的算出这个字节数:#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4),使用方法如下:long lLineBytes = WIDTHBYTES(m_lWidth * 24)。如果是8位的bmp图像,或4位,2位,则将算式中的24改为相应的位数即可。另外bmp文件分文件头14字节,信息40字节,调色板若干字节,数据lLineBytes*height字节。 其次,怎样访问bmp图像中特定的一个第x行,第y列的像素。如果bmp文件的文件头BITMAPINFOHEADER中的biHeight为正,则文件的起始位置是图像的左下角,像素依次从左向右,从下到上排列。如果biHeight为负,则文件的起始位置是图像的左上角,像素依次从左向右,从上到下排列。 假设指向bmp文件的数据区的指针byte* p_bmpdata, bmp文件为24位,为了访问第x行,第y列,可以用如下公式: b = p_bmpdata[lLineBytes * x + y*3]; g = p_bmpdata[lLineBytes *x + y*3+1]; r = p_bmpdata[lLineBytes * x + y*3+2]; 如果要从第一个像素开始遍历整个数据区,可以用这样的方式: for (i = 0; i <height; i ++) { for (j = 0; j < width; j ++) { b = p_bmpdata[lLineBytes * i + j*3]; g = p_bmpdata[lLineBytes * i + j*3+1]; r = p_bmpdata[lLineBytes * i + j*3+2]; } } 注意,这里的height和width是图像的列像素数和行像素数。而循环内与i相乘的是整行的字节数,不是行像素数。 最后,当使用自己建立的图像缓冲区是,遍历的方法又有所不同。假设为上bmp文件的数据作一个缓冲。 byte *p_bmpbuffer = new byte[height*width*3]; for(i = 0; i< width; i++) { for(j=0; j<height; j++) { p_bmpbuffer[(i*height+j)*3] = p_bmpdata[lLineBytes * i + j*3]; p_bmpbuffer[(i*height+j)*3+1] = p_bmpdata[lLineBytes * i + j*3+1]; p_bmpbuffer[(i*height+j)*3+2] = p_bmpdata[lLineBytes * i + j*3+2]; } } //这里不用memcpy的原因是p_bmpdata的长度为height*lLineBytes, 而p_bmpbuffer的长度是height*width*3, p_bmpdata在每行末都有1,2或3个字节的冗余。 byte b, g, r; for(i = 0; i< height; i++) { for(j=0; j<width; j++) { b = p_bmpbuffer[(i*width+j)*3]; g = p_bmpbuffer[(i*width+j)*3+1]; r = p_bmpbuffer[(i*width+j)*3+2]; } } //这里对像素的访问i,j的意义发生变化,之前的循环顺序访问下来,图像的行被列值y中间截断,虽然可以顺序依次访问每个像素,但i,j值没有实际意义。第二次循环, //i值即对应当前像素y坐标,j值对应当前像素x坐标。这样在运用模板滤波时,或是进行相邻像素计算时是准确的。 看了一本关于数字图像处理的书,提供的计算每行像素所占字节数算法如下: #define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4) LineBytes=WIDTHBYTES(m_nWidth*8); //m_nWidth是图像的宽度朋友们乍看可能不明白为何用此公式,现在解释一下公式。 BMP文件在保存或显示的时候,要求每行字节数是4的整数倍。WIDTHBYTES(bits)这个定义的意思就是为了保证每行都是4的倍数,注意必须要这样,要不然你显示的图片内容就会错位。 比如灰度图 吧,每行宽度是191个象素即191bytes,如果要保存所有图像信息,你在写文件时每行字节数必须为192=(191*1+3)/4*4;(也可通过位运算:(191*1+3)&~3);如果是24位的彩色图,即每个象素有 3个Bytes,只要把上式中*1换成*3即可。 每一行的字节数必须是4的整倍数,只要调用 WIDTHBYTES(bi.biWidth*bi.biBitCount)就能完成这一换算。举一个例子,对于2色图,如果图象宽是31,则每一行需要31位存储,合3个字节加7位,因 为字节数必须是4的整倍数,所以应该是4,而此时的biWidth=31,biBitCount=1,WIDTHBYTES(31*1)=4,和我们设想的一样。再举一个256色的例子,如果图象宽是31,则每一行需要31个字节存储,因为字 节数必须是4的整倍数,所以应该是32,而此时的biWidth=31,biBitCount=8,WIDTHBYTES(31*8)=32,和我们设想的一样。你可以多举几个例子来验证一下LineBytes为每一行的字节数 LineBytes= (DWORD)WIDTHBYTES(bi.biWidth*bi.biBitCount);不过只要理解了BMP的格式,自己就可以写出其他的换算函数。

继续阅读