BMP格式以及用纯C实现Load和Save

来源:互联网 发布:ios更新无法连接网络 编辑:程序博客网 时间:2024/05/09 03:14

BMP格式以及用纯C实现Load和Save

1 存储结构

BMP文件存储结构的格式可以在Windows中的WINGDI.h文件中找到定义。

BMP文件总体上由4部分组成,分别是位图文件头、位图信息头、调色板和图像数据,如表5-1所示。

BMP文件的组成结构

位图文件头(bitmap-file header)

位图信息头(bitmap-information header)

彩色表/调色板(color table)

位图数据(bitmap-data)

下面来详细看一下每个组成部分的细节。

1.1 位图文件头(bitmap-file header)

位图文件头(bitmap-file header)包含了图像类型、图像大小、图像数据存放地址和两个保留未使用的字段。

打开WINGDI.h文件,搜索"BITMAPFILEHEADER"就可以定位到BMP文件的位图文件头的数据结构定义。

typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;

tagBITMAPFILEHEADER结构

字 段 名

大小(单位:字节)

描 述

bfType

2

位图类别,根据不同的操作

系统而不同,在Windows

中,此字段的值总为'BM'

bfSize

4

BMP图像文件的大小

bfReserved1

2

总为0

bfReserved2

2

总为0

bfOffBits

4

BMP图像数据的地址

1.2 位图信息头(bitmap-information header)

位图信息头(bitmap-information header)包含了位图信息头的大小、图像的宽高、图像的色深、压缩说明图像数据的大小和其他一些参数。

打开WINGDI.h文件,搜索"tagBITMAPINFOHEADER"就可以定位到BMP文件的位图信息头的数据结构定义。

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;

tagBITMAPFILEHEADER结构

字 段 名

大小

(单位:

字节)

描 述

biSize

4

本结构的大小,根据不同的操作系统而不同,在Windows中,此字段的值总为28h字节=40字节

biWidth

4

BMP图像的宽度,单位像素

biHeight

4

总为0

biPlanes

2

总为0

biBitCount

2

BMP图像的色深,即一个像素用多少位表示,常见有1、4、8、16、24和32,分别对应单色、16色、256色、16位高彩色、24位真彩色和32位增强型真彩色

biCompression

4

压缩方式,0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定

biSizeImage

4

BMP图像数据大小,必须是4的倍数,图像数据大小不是4的倍数时用0填充补足

biXPelsPerMeter

4

水平分辨率,单位像素/m

biYPelsPerMeter

4

垂直分辨率,单位像素/m

biClrUsed

4

BMP图像使用的颜色,0表示使用全部颜色,对于256色位图来说,此值为100h=256

biClrImportant

4

重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,当显卡不能够显示所有颜色时,此值将辅助驱动程序显示颜色


2 BMP种类

按照单个像素占用的位数,有1,4,8,16,24,32几种

常用的几种说明如下:

(1)8位,灰度图,像素值范围[0,255]

(2)16位

biBitCount=16 表示位图最多有216种颜色。每个色素用16位(2个字节)表示。这种格式叫作高彩色,或叫增强型16位色,或64K色。它的情况比较复杂,当biCompression成员的值是BI_RGB时,它没有调色板。16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,最高的一位保留,设为0。这种格式也被称作555 16位位图。如果biCompression成员的值是BI_BITFIELDS,那么情况就复杂了,首先是原来调色板的位置被三个DWORD变量占据,称为红、绿、蓝掩码。分别用于描述红、绿、蓝分量在16位中所占的位置。在Windows 95(或98)中,系统可接受两种格式的位域:555和565,在555格式下,红、绿、蓝的掩码分别是:0x7C00、0x03E0、0x001F,而在565格式下,它们则分别为:0xF800、0x07E0、0x001F。你在读取一个像素之后,可以分别用掩码"与"上像素值,从而提取出想要的颜色分量(当然还要再经过适当的左右移操作)。在NT系统中,则没有格式限制,只不过要求掩码之间不能有重叠。(注:这种格式的图像使用起来是比较麻烦的,不过因为它的显示效果接近于真彩,而图像数据又比真彩图像小的多,所以,它更多的被用于游戏软件)。

(3)24位,一个通道占用一个字节

(4)32位,32位的BMP一个象素占3个字节,还有8位的alpha

3 C语言实现Load和Save

3.1 从磁盘读入BMP文件代码

// 自定义结构体

typedef struct s_MIImage
{
byte* pData; // 图像数据
int nImgW; // 图像宽度
int nImgH; // 图像高度
int nBits; // 1,4,8,24,32
BOOL bInit; // 是否已进行初始化,TRUE:已初始化。不能直接用指针来判断pData是否已初始化,因为pData的初始值不为NULL
// 也不能用(!bInit)来判断是否初始化,必须用(TRUE!=bInit)
}s_MIImage;

int MILoadBMP(OUT s_MIImage* pMIImg, char* pImgPath)
{
BITMAPFILEHEADER bf; //BMP文件头结构体
BITMAPINFOHEADER bi; //BMP信息头结构体

FILE* fp; //指向文件的指针
RGBQUAD *ipRGB = NULL;
DWORD LineBytes; // 每行的字节数
DWORD ImgBytesSize; // 图像总字节数
DWORD NumColors;
int i;

// 先释放图像空间
if (pMIImg->bInit == TRUE)
{
// 不能用FREE,因为pMIImg->pData的初始值不为NULL
free(pMIImg->pData);
}
pMIImg->pData = NULL;

// 打开文件
fp=fopen(pImgPath,"rb");
if(fp == NULL)
{
return -1;
}

//读取信息头、文件头
fread(&bf,sizeof(BITMAPFILEHEADER),1,fp); //把指针fp所指向的文件的头信息写入bf(地址)
fread(&bi,sizeof(BITMAPINFOHEADER),1,fp);

// 计算行字节数和总字节数
LineBytes=(DWORD)WIDTHBYTES(bi.biWidth*bi.biBitCount); //计算位图的实际宽度并确保它为32的倍数
ImgBytesSize=(DWORD)LineBytes*bi.biHeight;

if (bi.biClrUsed != 0 )
{
NumColors=(DWORD)bi.biClrUsed;
}
else
{
switch (bi.biBitCount)
{
case 1:
NumColors=2;
break;
case 4:
NumColors=16;
break;
case 8:
NumColors=256;
break;
case 24:
NumColors=0;
break;
case 32:
NumColors=0;
break;
}
}

//分配调色板内存
if ( (bi.biBitCount!=24) && (bi.biBitCount!=32) )
{
ipRGB=(RGBQUAD *)malloc(NumColors*sizeof(RGBQUAD));
fread(ipRGB,sizeof(RGBQUAD),NumColors,fp);
}

// 初始化图像
pMIImg->pData = (byte*)malloc(sizeof(byte) * ImgBytesSize); // 分配图像内存, 外部释放
if (!pMIImg->pData)
{
FREE(ipRGB);
return -1;
}
pMIImg->nBits = bi.biBitCount;
pMIImg->nImgW = bi.biWidth;
pMIImg->nImgH = bi.biHeight;
pMIImg->bInit = TRUE;

if ( (bi.biBitCount==32) || (bi.biBitCount==24) )
{
fseek(fp, 4, SEEK_CUR); //sizeof(RGBQUAD)
for (i=pMIImg->nImgH-1; i>=0; --i)
{
fread(pMIImg->pData+i*LineBytes, 1, LineBytes, fp);
}
}
else
{
for (i=pMIImg->nImgH-1; i>=0; --i)
{
fread(pMIImg->pData+i*LineBytes, 1, LineBytes, fp);
}
}
fclose(fp);
fp = NULL;

FREE(ipRGB);

return 0;
}

3.2 将内存中的图像数组写入磁盘BMP

int MISaveBMP(OUT char* pImgPath, s_MIImage* pMIImg)
{
BITMAPFILEHEADER bf; //BMP文件头结构体
BITMAPINFOHEADER bi; //BMP信息头结构体
RGBQUAD *ipRGB = NULL;
DWORD NumColors;
int i;
DWORD nLineBytes = (DWORD)WIDTHBYTES(pMIImg->nImgW * pMIImg->nBits);

FILE* fp = NULL;

if ( (!pImgPath) || (!pImgPath) || (TRUE!=pMIImg->bInit))
{
return -1;
}

// 写入另一个文件
fp = fopen(pImgPath, "wb");

memset(&bf, 0, sizeof(bf));
*((char*)&(bf.bfType)) = 'B';
*(((char*)&(bf.bfType))+1) = 'M';
bf.bfReserved1 = 0;
bf.bfReserved2 = 0;
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bf.bfSize = pMIImg->nImgW * pMIImg->nImgH * (pMIImg->nBits>>3) + bf.bfOffBits;

memset(&bi, 0, sizeof(bi));
bi.biSize = sizeof(bi);
bi.biBitCount = pMIImg->nBits;
bi.biWidth = pMIImg->nImgW;
bi.biHeight = pMIImg->nImgH;
bi.biCompression = BI_RGB;
bi.biPlanes = 1;
bi.biClrUsed = 0;

fwrite(&bf,sizeof(BITMAPFILEHEADER),1,fp);
fwrite(&bi,sizeof(BITMAPINFOHEADER),1,fp);

if (bi.biClrUsed != 0 )
{
NumColors=(DWORD)bi.biClrUsed;
}
else
{
switch (bi.biBitCount)
{
case 1:
NumColors=2;
break;
case 4:
NumColors=16;
break;
case 8:
NumColors=256;
break;
case 24:
NumColors=0;
break;
case 32:
NumColors=0;
break;
}
}

//分配调色板内存
if ( (bi.biBitCount!=24) && (bi.biBitCount!=32) )
{
ipRGB=(RGBQUAD *)malloc(NumColors*sizeof(RGBQUAD));
fread(ipRGB,sizeof(RGBQUAD),NumColors,fp);
}

if ( (bi.biBitCount!=24) && (bi.biBitCount!=32) )
{
fwrite(ipRGB,sizeof(RGBQUAD),NumColors,fp);
for (i=(bi.biHeight)-1 ;i>=0;i--)
{
fwrite(pMIImg->pData+nLineBytes*i, sizeof(byte), nLineBytes, fp);
}
}
else
{
for (i=(bi.biHeight)-1 ;i>=0;i--)
{
fwrite(pMIImg->pData+nLineBytes*i, sizeof(byte), nLineBytes, fp);
}
}

fclose(fp);
fp = NULL;
FREE(ipRGB);
return 0;
}

[注:此文档非原创,文字资料均收集于互联网,代码本人有改动]

原创粉丝点击