1 认识BMP文件
bmp文件结构:
第一部分:位图文件头BITMAPFILEHEADER,是一个结构。这个结构的长度是固定的,为14个字节(WORD为无符号16位整数,DWORD为无符号32位整数)。
其定义如下:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;//位图文件类型,必须是0x4D42,即字符串“BM”,也就是说所有.bmp文件的头两个字节都是“BM”
DWORD bfSize;//位图文件大小,包括这14个字节
WORD bfReserved1;//保留字,设为0
WORD bfReserved2;//保留字,设为0
DWORD bfOffBits;//从文件头到实际的位图数据的偏移字节数,单位:字节
} BITMAPFILEHEADER;
第二部分:位图信息头BITMAPINFOHEADER,是一个结构。这个结构的长度也是固定的,为40个字节(LONG为32位整数)。
其定义如下:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;//本结构所占用字节数,大小为40字节
LONG biWidth;//位图宽度,单位:字节
LONG biHeight;//位图高度,单位:字节
WORD biPlanes;//目标设备级别,必须为1
WORD biBitCount;//表示颜色时每个像素要用到的位数,常用的值为1(黑白二色图), 4(16色图), 8(256色), 24(真彩色图)
DWORD biCompression;// 位图是否压缩,其类型是 0(BI_RGB不压缩), 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)
DWORD biSizeImage;//实际的位图数据占用的字节数
LONG biXPelsPerMeter;//位图水平分辨率,每米像素数
LONG biYPelsPerMeter;//位图垂直分辨率,每米像素数
DWORD biClrUsed;//指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为2的biBitCount次幂个
DWORD biClrImportant;//指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的
} BITMAPINFOHEADER;
第三部分:调色板Palette。这里是对那些需要调色板的位图文件而言的,有些位图,如真彩色图,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。
调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有2的biBitCount次幂个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:
typedef struct tagRGBQUAD {
BYTE rgbBlue; //该颜色的蓝色分量(值范围为0-255)
BYTE rgbGreen; //该颜色的绿色分量(值范围为0-255)
BYTE rgbRed; //该颜色的红色分量(值范围为0-255)
BYTE rgbReserved; //保留值,设为0
} RGBQUAD;
第四部分:实际的图象数据。对于用到调色板的位图,图象数据就是该象素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。
对于2色位图,用1位就可以表示该象素的颜色(一般0表示黑,1表示白),所以一个字节可以表示8个象素。
对于16色位图,用4位可以表示一个象素的颜色,所以一个字节可以表示2个象素。
对于256色位图,一个字节刚好可以表示1个象素。
对于真彩色图,三个字节表示1个象素。
注意:
(1) 每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。
(2) 一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图象最下面一行的左边第一个象素,然后是左边第二个象素……接下来是倒数第二行左边第一个象素,左边第二个象素……依次类推 ,最后得到的是最上面一行的最右一个象素。
2 编程
#include <iostream>
#include <Windows.h>
using std::cout;
using std::endl;
unsigned char * pData;
int width;
int height;
int bitCount;
RGBQUAD * pRGBQUAD;
bool readBmpFile(char * filename)
{
FILE * pf;
pf = fopen(filename, "rb");
if (NULL == pf)
{
cout << "文件打开失败!" << endl;
fclose(pf);
return false;
}
BITMAPFILEHEADER bitMapFileHeader;
BITMAPINFOHEADER bitMapInfoHeader;
fread(&bitMapFileHeader, sizeof(BITMAPFILEHEADER), 1, pf);
if (0x4D42 != bitMapFileHeader.bfType)
{
cout << "此文件不是BMP文件!" << endl;
return false;
}
fread(&bitMapInfoHeader, sizeof(BITMAPINFOHEADER), 1, pf);
cout << "位图文件头:" << endl;
cout << "位图文件类型: " << bitMapFileHeader.bfType << endl;
cout << "位图文件大小: " << bitMapFileHeader.bfSize << endl;
cout << "偏移的字节数: " << bitMapFileHeader.bfOffBits << endl;
width = bitMapInfoHeader.biWidth;
height = bitMapInfoHeader.biHeight;
bitCount = bitMapInfoHeader.biBitCount;
cout << "\n位图信息头:" << endl;
cout << "信息头占用字节数:" << bitMapInfoHeader.biSize << endl;
cout << "位图宽度: " << bitMapInfoHeader.biWidth << endl;
cout << "位图高度: " << bitMapInfoHeader.biHeight << endl;
cout << "位图压缩类型: " << bitMapInfoHeader.biCompression << endl;
cout << "位图每像素占用位数: " << bitMapInfoHeader.biBitCount << endl;
cout << "位图数据占用字节数: " << bitMapInfoHeader.biSizeImage << endl;
if (8 == bitMapInfoHeader.biBitCount)
{
pRGBQUAD = new RGBQUAD[256];
fread(pRGBQUAD, sizeof(RGBQUAD), 256, pf);
}
//数据每行字节数为4的倍数
int lineByte = (bitMapInfoHeader.biWidth * bitMapInfoHeader.biBitCount / 8 + 3) / 4 * 4;
pData = new unsigned char[bitMapInfoHeader.biHeight * lineByte];
fread(pData, sizeof(unsigned char), bitMapInfoHeader.biHeight * lineByte, pf);
fclose(pf);
return true;
}
bool writeBmpFile(char * filename, unsigned char * pData, int biWidth, int biHeight, int biBitCount)
{
FILE * pf;
pf = fopen(filename, "wb");
if (NULL == pf)
{
cout << "文件打开失败!" << endl;
fclose(pf);
return false;
}
int colorTablesize = 0;
if (biBitCount == 8)
colorTablesize = 1024;
//待存储图像数据每行字节数为4的倍数
int lineByte = (biWidth * biBitCount / 8 + 3) / 4 * 4;
//申请位图文件头结构变量,填写文件头信息
BITMAPFILEHEADER bitMapFileHeader;
bitMapFileHeader.bfType = 0x4D42;//bmp类型
bitMapFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorTablesize + lineByte*biHeight;
bitMapFileHeader.bfReserved1 = 0;
bitMapFileHeader.bfReserved2 = 0;
bitMapFileHeader.bfOffBits = 54 + colorTablesize;
//申请位图信息头结构变量,填写信息头信息
BITMAPINFOHEADER bitMapInfoHeader;
bitMapInfoHeader.biBitCount = biBitCount;
bitMapInfoHeader.biClrImportant = 0;
bitMapInfoHeader.biClrUsed = 0;
bitMapInfoHeader.biCompression = 0;
bitMapInfoHeader.biHeight = biHeight;
bitMapInfoHeader.biPlanes = 1;
bitMapInfoHeader.biSize = 40;
bitMapInfoHeader.biSizeImage = lineByte * biHeight;
bitMapInfoHeader.biWidth = biWidth;
bitMapInfoHeader.biXPelsPerMeter = 0;
bitMapInfoHeader.biYPelsPerMeter = 0;
//写文件头进文件
fwrite(&bitMapFileHeader, sizeof(BITMAPFILEHEADER), 1, pf);
//写位图信息头进内存
fwrite(&bitMapInfoHeader, sizeof(BITMAPINFOHEADER), 1, pf);
//如果灰度图像,有颜色表,写入文件
if (biBitCount == 8)
{
fwrite(pRGBQUAD, sizeof(RGBQUAD), 256, pf);
}
fwrite(pData, sizeof(unsigned char), biHeight * lineByte, pf);
fclose(pf);
return true;
}
int main()
{
char * toReadfilename = "read.bmp";
bool ret = readBmpFile(toReadfilename);
if (!ret)
{
cout << "读BMP文件失败!" << endl;
return -1;
}
else
{
cout << "读BMP文件成功!" << endl;
}
cout << endl;
char * toWritefilename = "write.bmp";
ret = writeBmpFile(toWritefilename, pData, width, height, bitCount);
if (!ret)
{
cout << "写BMP文件失败!" << endl;
return -1;
}
else
{
cout << "写BMP文件成功!" << endl;
}
delete[] pRGBQUAD;
pRGBQUAD = NULL;
delete[] pData;
pData = NULL;
return 0;
}