BITMAP文件大体上分成四个部分,如下表所示。
文件部分 | 长度(字节) |
位图文件头 Bitmap File Header | 14 |
位图信息数据头 Bitmap Info Header | 40 |
调色板 Palette | 4*n (n≥0) |
位图数据 Image Data | 不定长 |
BITMAP文件格式
1、位图文件头 Bitmap File Header
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
字段 | 说明 |
bfType | 文件类型,必须是0x4D42,即字符串“BM” |
bfSize | 指定文件大小,以字节为单位,包括本结构体长度 |
bfReserved1 | 保留字,必须设置为0 |
bfReserved2 | 保留字,必须设置为0 |
bfOffBits | 从文件头开始到实际的图象数据的偏移字节数。 |
2、位图信息数据头 Bitmap Info Header
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;
字段 | 说明 |
biSize | 该结构体长度。 |
biWidth | 说明图象的宽度,单位为象素。 |
biHeight | 说明图象的高度,单位为象素。 |
biPlanes | 位平面数,必须为1。 |
biBitCount | 指定颜色位数,1为二值,4为16色,8为256色,16、24、32为真彩色。 |
biCompression | 指定是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4、BI_BITFIELDS。 |
biSizeImage | 实际的位图数据占用的字节数。 |
biXPelsPerMeter | 目标设备水平分辨率,单位是每米的像素数。 |
biYPelsPerMeter | 目标设备垂直分辨率,单位是每米的像素数。 |
biClrUsed | 实际使用的颜色数。 |
biClrImportant | 图像中重要的颜色数,若该值为0,则所有的颜色都是重要的。 |
3、调色板 Palette
第三部分为调色板(Palette),当然,这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。
调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有2的biBitCount次方个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:
typedef struct tagRGBQUAD {
BYTE rgbBlue; //该颜色蓝色分量
BYTE rgbGreen; //该绿色蓝色分量
BYTE rgbRed; //该红色蓝色分量
BYTE rgbReserved; //保留
} RGBQUAD;
4、位图数据 ImageData
第四部分就是实际的图象数据了。对于用到调色板的位图,图象数据就是该像素颜在调色板中的索引值,对于真彩色图,图象数据就是实际的R,G,B值。下面就2色,16色,256色位图和真彩色位图分别介绍。
- 2色位图:用1位就可以表示该像素的颜色(一般0表示黑,1表示白),所以一个字节可以表示8个像素。
- 16色位图:用4位可以表示一个像素的颜色,所以一个字节可以表示2个像素。
- 256色位图:一个字节刚好可以表示1个像素。
- 真彩色图:三个字节才能表示1个像素。
例子
以一幅24位真彩色图像为例,读取并顺时针旋转并保存。该BITMAP图片尺寸为450 * 227。
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#pragma pack(push)
#pragma pack(1)
//24位真彩色
struct RGB
{
BYTE b;
BYTE g;
BYTE r;
};
#pragma pack(pop)
void imgOpr(RGB &a, RGB &b)
{
b.b = a.b;
b.g = a.g;
b.r = a.r;
}
//二维数组申请img[m][n]
RGB ** RGB_new(int m, int n)
{
int i;
RGB **img;
img = new RGB * [m];
for (i = 0; i < m; i ++)
{
img[i] = new RGB [n];
}
return img;
}
//释放img[m][n]
void RGB_delete(RGB **img, int m, int n)
{
int i;
for (i = 0; i < m; i ++)
{
delete [] img[i];
}
delete [] img;
}
int main()
{
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
RGB **img1;
RGB **img2;
BYTE *tmp;
int i, j;
int height, width;
int m, n;
FILE *fp, *fq;
errno_t err;
err = fopen_s(&fp, "D:\\Work\\Debug\\7.bmp", "rb");
if (err == 0)
{
printf("The input file was opened!\n");
}
else
{
printf("The input file was not opened!\n");
return 1;
}
err = fopen_s(&fq, "D:\\Work\\Debug\\2.bmp", "wb");
if (err == 0)
{
printf("The output file was opened!\n");
}
else
{
printf("The output file was not opened!\n");
return 1;
}
fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fp);
fread(&bih, sizeof(BITMAPINFOHEADER), 1, fp);
height = bih.biHeight;
width = bih.biWidth;
img1 = RGB_new(height, width);
img2 = RGB_new(width, height);
//计算每行图像字节数,必须是4的整倍数,不够需要填充
m = (bih.biWidth * bih.biBitCount + 31) / 32 * 4;
n = (bih.biHeight * bih.biBitCount + 31) / 32 * 4;
tmp = new BYTE [n];
memset(tmp, 0x0, n);
//对图片进行操作
for (i = 0; i < height; i ++)
{
//跳过调色板数据段,并寻址
fseek(fp, bfh.bfOffBits + i * m, SEEK_SET);
fread(img1[i], sizeof(RGB), width, fp);
for (j = 0; j < width; j ++)
{
//将数组 img 赋值给 img2
imgOpr(img1[i][j], img2[j][i]);
}
}
//更新BITMAPINFOHEADER结构体
bih.biHeight = width;
bih.biWidth = height;
bih.biSizeImage = n * width;
//将修改后的图片保存到文件
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fq);
fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fq);
for (i = 0; i < width; i ++)
{
memcpy(tmp, img2[i], sizeof(RGB) * height);
fwrite(tmp, 1, n, fq);
}
fclose(fp);
fclose(fq);
//释放
RGB_delete(img1, height, width);
RGB_delete(img2, width, height);
delete [] tmp;
system("pause");
return 0;
}
原图
顺时针旋转90度