##BMP图片格式:
BMP图片,是Bitmap(位图)的简称,它是windows显示图片的基本格式。在windows下,任何格式的图片文件(包括视频播放)都要转化为位图才能显示出来。各种格式的图片文件也都是在位图格式的基础上采用不同的压缩算法生成的。
位图文件主要分为如下3个部分:
块名称 |
对应Windows结构体定义 |
大小(Byte) |
1.文件信息头 |
BITMAPFILEHEADER |
14 |
2.位图信息头 |
BITMAPINFOHEADER |
40 |
3.RGB颜色阵列 |
BYTE* |
由图像长宽尺寸决定 |
1.文件信息头BITMAPFILEHEADER
结构体定义如下:
typedef struct tagBITMAPFILEHEADER { /* bmfh */
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
bfType:说明文件的类型,该值必需是0x4D42(十六进制ASCII码4D代表“B”,42代表“M”),也就是字符'BM'。
bfSize:说明该位图文件的大小,用字节为单位,包括这14个字节
bfReserved1:保留,必须设置为0
bfReserved2:保留,必须设置为0
bfOffBits:说明从文件头开始到实际的图象数据之间的字节的偏移量。这个参数是非常有用的,因为位图信息头和调色板的长度会根据不同情况而变化,所以你可以用这个偏移值迅速的从文件中读取到位数据。
备注:WORD为无符号16位二进制整数(2byte),DWORD为无符号32位二进制整数(4byte),所以以上总共为14个字节
2.位图信息头BITMAPINFOHEADER
结构体定义如下:
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;
biSize:说明BITMAPINFOHEADER结构所需要的字数。
biWidth:说明图象的宽度,以象素为单位。
biHeight:说明图象的高度,以象素为单位。注:这个值除了用于描述图像的高度之外,它还有另一个用处,就是指明该图像是倒向的位图,还是正向的位图。如果该值是一个正数,说明图像是倒向的,如果该值是一个负数,则说明图像是正向的。大多数的BMP文件都是倒向的位图,也就是说,高度值是一个正数。
biPlanes:为目标设备说明位面数,其值将总是被设为1。
biBitCount:说明比特数/象素,其值为1、4、8、16、24、或32。我们平时用到的图像绝大部分是24位和32位的;
1:单色位图
4:16色位图
8:256色位图
16:16bit高彩色位图
24:24bit真彩色位图
32:32bit增强型真彩色位图
biCompression:说明图象数据压缩的类型,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS,这里我们只讨论没有压缩的类型:BI_RGB
0:不压缩(用BI_RGB表示)
1:RLE8,使用8位RLE压缩方式(用BI_RLE8表示)
2:RLE4,使用4位RLE压缩方式(用BI_RLE4表示)
3:Bitfields:位域存放方式(用BI_BITFIELDS表示)
4:JPEG图像(也用BI_JPEG表示)
5:PNG图像(也用BI_PNG表示)
biSizeImage:说明图象的大小,以字节为单位。当用BI_RGB格式时,可设置为0。
biXPelsPerMeter:说明水平分辨率,用象素/米表示;目标设备水平分辨率,单位是每米的像素数
biYPelsPerMeter:说明垂直分辨率,用象素/米表示;目标设备垂直分辨率,单位是每米的像素数
biClrUsed:说明位图实际使用的彩色表中的
颜色索引数(设为0的话,则说明使用所有调色板项)。
biClrImportant:说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。
备注:这个结构的长度是固定的,为40个字节(LONG为32位二进制整数)

Figure 1. 使用ultraEdit打开一个5*5且仅有G分量的bmp图片
1).00-1F-00:分别是RGB的数值,表示最后一行第一列的RGB值,然后第二列....第五列;
2)00:表示补充的字节数;
3)00-20-00:表示倒数第二行行第一列的RGB值,然后第二列....第五列;然后进入2)
字节 #0-1 保存位图文件的标识符,这两个字节的典型数据是BM。
字节 #2-5 使用一个dword保存位图文件大小。
字节 #6-9 是保留部分,留做以后的扩展使用,对实际的解码格式没有影响。
字节 #10-13 保存位图数据位置的地址偏移,也就是起始地址。
字节 #14-17 定义以下用来描述影像的区块(BitmapInfoHeader)的大小。它的值是:40 - Windows 3.2、95、NT、12 - OS/2 1.x、240 - OS/2 2.x
字节 #18-21 保存位图宽度(以像素个数表示)。
字节 #22-25 保存位图高度(以像素个数表示)。
字节 #26-27 保存所用彩色位面的个数。不经常使用。
字节 #28-29 保存每个像素的位数,它是图像的颜色深度。常用值是1、4、8(灰阶)和24(彩色)。
字节 #30-33 定义所用的压缩算法。允许的值是0、1、2、3、4、5。
字节 #34-37 保存图像大小。这是原始(en:raw)位图数据的大小,不要与文件大小混淆。
字节 #38-41 保存图像水平方向分辨率。
字节 #42-45 保存图像竖直方向分辨率。
字节 #46-49 保存所用颜色数目。
字节 #50-53 保存所用重要颜色数目。当每个颜色都重要时这个值与颜色数目相等。
【起始点的00-00-00,表示实际上最后一行的第一列的RGB值,然后在读取下一个像素点的值,以此类推,读完倒数第一行之后,再读取倒数第二行的数据,因此file文件最后一个8bit数据应该是图像的第一行的最后一个像素点的B分量值】
信息 |
十六进制信息 |
意义 |
bfType |
4D-42 |
bmp头信息 |
bfSize |
00-5E-EC-36 |
bfSize为62200854个字节 |
bfReserved1 |
00-00 |
0个字节 |
bfReserved2 |
00-00 |
0个字节 |
bfOffBits |
00-00-00-36 |
54个字节 |
DWORD biSize |
00-00-00-28 |
40个字节 |
LONG biWidth |
00-00-04-38 |
1080个字节 |
LONG biHeight |
00-00-07-80 |
1920个字节 |
WORD biPlanes |
00-01 |
1个字节 |
WORD biBitCount |
00-18 |
24个字节 |
DWORD biCompression |
00-00-00-00 |
0个字节 |
DWORD biSizeImage |
00-5E-EC-00 |
6220800个字节 |
LONG biXPelsPerMeter |
00-00-00-00 |
0个字节 |
LONG biYPelsPerMeter |
00-00-00-00 |
0个字节 |
DWORD biClrUsed |
00-00-00-00 |
0个字节 |
DWORD biClrImportant |
00-00-00-00 |
0个字节 |
##3.RGB颜色通道**
typedef struct tagRGBQUAD
{
BYTE rgbBlue; //该颜色的蓝色分量
BYTE rgbGreen; //该颜色的绿色分量
BYTE rgbRed; //该颜色的红色分量
BYTE rgbReserved;//保留值
} RGBQUAD;
色深24位的位图
BMP图像数据区在读取时,位图数据的偏移量为sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)。由于Windows在进行行扫描的时候最小的单位为4个字节,所以当 图片宽 X 每个像素的字节数 != 4的整数倍时要在每行的后面补上缺少的字节,以0填充(一般来说当图像宽度为2的幂时不需要对齐)。位图文件里的数据在写入的时候已经进行了行对齐,也就是说加载的时候不需要再做行对齐。但是这样一来图片数据的长度就不是:宽 X 高 X 每个像素的字节数 了,我们需要通过下面的方法计算正确的数据长度:
//Calculate the image data size
int iLineByteCnt = (((m_iImageWidth*m_iBitsPerPixel) + 31) >> 5) << 2;
m_iImageDataSize = iLineByteCnt * m_iImageHeight;
如5
5的bmp图像,我们需要在每行的结尾处加“offset = (channelswidth)%4”个字节的0,当offset=1时,需要补充0x00,即一个字节
【起始点的00-00-00,表示实际上最后一行的第一列的RGB值,然后在读取下一个像素点的值,以此类推,读完倒数第一行之后,再读取倒数第二行的数据,因此file文件最后一个8bit数据应该是图像的第一行的最后一个像素点的B分量值】
Figure 1. bmp结构(wiki)
读取和保存bmp图片的测试程序:其包含三个文件:头文件:BmpInfo.h;主文件主函数:main.c;读写文件读写图像:readWrite.cpp
需要注意的是markdown填写程序遇到半角小于号,其之后的代码都显示不出来了,为了将代码显示出,以下程序的小于号全都改成了全角下的。
//建立头文件BmpInfo.h
#ifndef BMPINFO_H
#define BMPINFO_H
typedef unsigned short int WORD;
typedef unsigned int DWORD;
typedef int LONG;
typedef unsigned char BYTE;
//*****************************************************************************
//* definition :struct
//* Description :位图文件头信息结构定义
//* 其中不包含文件类型信息(由于结构体的内存结构决定,要是加了的话将不能正确读取文件信息)
//*****************************************************************************/
typedef struct tagBITMAPFILEHEADER
{
//WORD bfType;////////////////文件类型,必须为BM(不包含文件类型信息)
DWORD bfSize;///////////////指定文件大小,以字节为单位(3-6字节,低位在前)
WORD bfReserved1;///////////文件保留字,必须为0
WORD bfReserved2;///////////文件保留字,必须为0
DWORD bfOffBits;////////////从文件头到实际位图数据的偏移字节数(11-14字节,低位在前)
}BITMAPFILEHEADER;
///*****************************************************************************
//* definition :struct
//* Description :位图信息头
//*****************************************************************************/
typedef struct tagBITMAPINFOHEADER
{
DWORD biSize;///////////////本结构所占用字节数,为40。注意:实际操作中则有44,这是字节补齐的原因
LONG biWidth;///////////////位图的宽度,以像素为单位
LONG biHeight;//////////////位图的高度,以像素为单位
WORD biPlanes;//////////////目标设备的级别,必须为1
WORD biBitCount;////////////每个像素所需的位数,1(双色),4(16色),8(256色)16(高彩色),24(真彩色)或32之一
DWORD biCompression;////////位图压缩类型,0(不压缩),1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
DWORD biSizeImage;//////////位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位
LONG biXPelsPerMeter;///////位图水平分辨率,每米像素数
LONG biYPelsPerMeter;///////位图垂直分辨率,每米像素数
DWORD biClrUsed;////////////位图实际使用的颜色表中的颜色数,若该值为0,则使用颜色数为2的biBitCount次方
DWORD biClrImportant;///////位图显示过程中重要的颜色数,若该值为0,则所有的颜色都重要
}BITMAPINFOHEADER;
/*********** *********** *********** *********** *********** *********** ***********
* definition :struct
* Description :调色板
*********** *********** *********** *********** *********** *********** ***********/
typedef struct tagRGBQUAD
{
BYTE rgbBlue;///////////////蓝色的亮度(0-255)
BYTE rgbGreen;//////////////绿色的亮度(0-255)
BYTE rgbRed;////////////////红色的亮度(0-255)
BYTE rgbReserved;///////////保留,必须为0
}RGBQUAD;
typedef struct
{
int width;
int height;
int channels;
unsigned char* imageData;
}Image;
Image* LoadImage(char* path);
bool SaveImage(char* path, Image* bmpImg);
#endif
//主函数main.c
#include "BmpInfo.h"
#include
int main()
{
Image* img = LoadImage("e:/test.bmp");
bool flag = SaveImage("e:/result.bmp", img);
if(flag)
{
printf("save ok... \n");
}
while(1);
return 0;
}
//readWrite.cpp
#include "BmpInfo.h"
#include
#include
Image* LoadImage(char* path)
{
Image* bmpImg;
FILE* pFile;
unsigned short fileType;
BITMAPFILEHEADER bmpFileHeader;
BITMAPINFOHEADER bmpInfoHeader;
int channels = 1;
int width = 0;
int height = 0;
int step = 0;
int offset = 0;
unsigned char pixVal;
RGBQUAD* quad;
int i, j, k;
bmpImg = (Image*)malloc(sizeof(Image));
pFile = fopen(path, "rb");
if (!pFile)
{
free(bmpImg);
return NULL;
}
fread(&fileType, sizeof(unsigned short), 1, pFile);
if (fileType == 0x4D42)
{
//printf("bmp file! \n");
fread(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, pFile);
/*printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n");
printf("bmp文件头信息:\n");
printf("文件大小:%d \n", bmpFileHeader.bfSize);
printf("保留字:%d \n", bmpFileHeader.bfReserved1);
printf("保留字:%d \n", bmpFileHeader.bfReserved2);
printf("位图数据偏移字节数:%d \n", bmpFileHeader.bfOffBits);*/
fread(&bmpInfoHeader, sizeof(BITMAPINFOHEADER), 1, pFile);
/*printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n");
printf("bmp文件信息头\n");
printf("结构体长度:%d \n", bmpInfoHeader.biSize);
printf("位图宽度:%d \n", bmpInfoHeader.biWidth);
printf("位图高度:%d \n", bmpInfoHeader.biHeight);
printf("位图平面数:%d \n", bmpInfoHeader.biPlanes);
printf("颜色位数:%d \n", bmpInfoHeader.biBitCount);
printf("压缩方式:%d \n", bmpInfoHeader.biCompression);
printf("实际位图数据占用的字节数:%d \n", bmpInfoHeader.biSizeImage);
printf("X方向分辨率:%d \n", bmpInfoHeader.biXPelsPerMeter);
printf("Y方向分辨率:%d \n", bmpInfoHeader.biYPelsPerMeter);
printf("使用的颜色数:%d \n", bmpInfoHeader.biClrUsed);
printf("重要颜色数:%d \n", bmpInfoHeader.biClrImportant);
printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n");*/
if (bmpInfoHeader.biBitCount == 8)
{
//printf("该文件有调色板,即该位图为非真彩色\n\n");
channels = 1;
width = bmpInfoHeader.biWidth;
height = bmpInfoHeader.biHeight;
offset = (channels*width)%4;
if (offset != 0)
{
offset = 4 - offset;
}
//bmpImg->mat = kzCreateMat(height, width, 1, 0);
bmpImg->width = width;
bmpImg->height = height;
bmpImg->channels = 1;
bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*height);
step = channels*width;
quad = (RGBQUAD*)malloc(sizeof(RGBQUAD)*256);
fread(quad, sizeof(RGBQUAD), 256, pFile);
free(quad);
for (i=0; i<height; i++)
{
for (j=0; j<width; j++)
{
fread(&pixVal, sizeof(unsigned char), 1, pFile);
bmpImg->imageData[(height-1-i)*step+j] = pixVal;
}
if (offset != 0)
{
for (j=0; j<offset; j++)
{
fread(&pixVal, sizeof(unsigned char), 1, pFile);
}
}
}
}
else if (bmpInfoHeader.biBitCount == 24)
{
//printf("该位图为位真彩色\n\n");
channels = 3;
width = bmpInfoHeader.biWidth;
height = bmpInfoHeader.biHeight;
bmpImg->width = width;
bmpImg->height = height;
bmpImg->channels = 3;
bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*3*height);
step = channels*width;
offset = (channels*width)%4;
if (offset != 0)
{
offset = 4 - offset;
}
//读取pFile位图开始的数据填充到数组作为最后一行数据,若图像宽度不是4的整数倍,则需要将补的offset个8bit0跳过,继续读取pFile数据放入数组倒数第二行。
for (i=0; i<height; i++)
{
for (j=0; j<width; j++)
{
for (k=0; k<3; k++)
{
fread(&pixVal, sizeof(unsigned char), 1, pFile);
bmpImg->imageData[(height-1-i)*step+j*3+k] = pixVal;
}
}
if (offset != 0)
{
for (j=0; j<offset; j++)
{
fread(&pixVal, sizeof(unsigned char), 1, pFile);
}
}
}
printf("%d\n", bmpImg->imageData[0]);//R
printf("%d\n", bmpImg->imageData[1]);//G
printf("%d\n", bmpImg->imageData[2]);//B
printf("%d\n", bmpImg->imageData[3]);//R从左到右第二个像素点的R
printf("%d\n", bmpImg->imageData[4]);//G
printf("%d\n", bmpImg->imageData[5]);//B
}
}
return bmpImg;
}
bool SaveImage(char* path, Image* bmpImg)
{
FILE *pFile;
unsigned short fileType;
BITMAPFILEHEADER bmpFileHeader;
BITMAPINFOHEADER bmpInfoHeader;
int step;
int offset;
unsigned char pixVal = '\0';
int i, j;
RGBQUAD* quad;
pFile = fopen(path, "wb");
if (!pFile)
{
return false;
}
fileType = 0x4D42;
fwrite(&fileType, sizeof(unsigned short), 1, pFile);
if (bmpImg->channels == 3)//24位,通道,彩图
{
step = bmpImg->channels*bmpImg->width;
offset = step%4;
if (offset != 4)
{
step += 4-offset;
}
bmpFileHeader.bfSize = bmpImg->height*step + 54;
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfOffBits = 54;
fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, pFile);
bmpInfoHeader.biSize = 40;
bmpInfoHeader.biWidth = bmpImg->width;
bmpInfoHeader.biHeight = bmpImg->height;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = 24;
bmpInfoHeader.biCompression = 0;
bmpInfoHeader.biSizeImage = bmpImg->height*step;
bmpInfoHeader.biXPelsPerMeter = 0;
bmpInfoHeader.biYPelsPerMeter = 0;
bmpInfoHeader.biClrUsed = 0;
bmpInfoHeader.biClrImportant = 0;
fwrite(&bmpInfoHeader, sizeof(BITMAPINFOHEADER), 1, pFile);
//先获取最后一行的数据从左到右,将其依次填入到pFile中,如果图像宽度不是4的整数倍,则需要不从offset个8bit0字节填入到pFile中
for (i=bmpImg->height-1; i>-1; i--)
{
for (j=0; j<bmpImg->width; j++)
{
pixVal = bmpImg->imageData[i*bmpImg->width*3+j*3];
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
pixVal = bmpImg->imageData[i*bmpImg->width*3+j*3+1];
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
pixVal = bmpImg->imageData[i*bmpImg->width*3+j*3+2];
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
}
if (offset!=0)
{
for (j=0; j<offset; j++)
{
pixVal = 0;
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
}
}
}
}
else if (bmpImg->channels == 1)//8位,单通道,灰度图
{
step = bmpImg->width;
offset = step%4;
if (offset != 4)
{
step += 4-offset;
}
bmpFileHeader.bfSize = 54 + 256*4 + bmpImg->width;
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfOffBits = 54 + 256*4;
fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, pFile);
bmpInfoHeader.biSize = 40;
bmpInfoHeader.biWidth = bmpImg->width;
bmpInfoHeader.biHeight = bmpImg->height;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = 8;
bmpInfoHeader.biCompression = 0;
bmpInfoHeader.biSizeImage = bmpImg->height*step;
bmpInfoHeader.biXPelsPerMeter = 0;
bmpInfoHeader.biYPelsPerMeter = 0;
bmpInfoHeader.biClrUsed = 256;
bmpInfoHeader.biClrImportant = 256;
fwrite(&bmpInfoHeader, sizeof(BITMAPINFOHEADER), 1, pFile);
quad = (RGBQUAD*)malloc(sizeof(RGBQUAD)*256);
for (i=0; i<256; i++)
{
quad[i].rgbBlue = i;
quad[i].rgbGreen = i;
quad[i].rgbRed = i;
quad[i].rgbReserved = 0;
}
fwrite(quad, sizeof(RGBQUAD), 256, pFile);
free(quad);
for (i=bmpImg->height-1; i>-1; i--)
{
for (j=0; j<bmpImg->width; j++)
{
pixVal = bmpImg->imageData[i*bmpImg->width+j];
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
}
if (offset!=0)
{
for (j=0; j<offset; j++)
{
pixVal = 0;
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
}
}
}
}
fclose(pFile);
return true;
}