首先,本篇文章参考了众多博客,有:
http://blog.csdn.net/embededvc/article/details/6737751
http://blog.chinaunix.net/uid-22277851-id-1777698.html
http://www.cnblogs.com/lzlsky/archive/2012/08/16/2641698.html
http://blog.csdn.net/lyy289065406/article/details/6717679
http://blog.csdn.net/carson2005/article/details/7614125 等等,通过概括,然后做出了一个可以生成4位深以及24位深bmp图片的程序(通过控制台来选择),并加了一些自己的感悟而成
(一)BMP图像格式
首先,BMP文件可分为四部分
位图文件头(BITMAPFILEHEADER) | 位图信息头(BITMAPINFOHEADER) | 颜色表(RgbQuad) | 文件数据(DATA) |
但是,有时候在处理图像时(如使用StretchDIBits函数时),需要一个BITMAPINFO结构,BITMAPINFO其实就是上面的位图文件头和颜色表的组合,如下
typedef struct tagBITMAPINFO // bmi{BITMAPINFOHEADER bmiHeader ; // info-header structureRGBQUAD bmiColors[1] ; // color table array}BITMAPINFO, * PBITMAPINFO ;
因此,BMP文件就被分成了三部分
位图文件头(BITMAPFILEHEADER) | 位图信息(BITMAPINFO) | 文件数据(DATA) |
但是呢,还有一种情况,就是位深为24时,是真彩色图片(下面会提到),此时没有颜色表,自身文件数据就能表示颜色。(下面也会详细说),依旧为三部分,如下
位图文件头(BITMAPFILEHEADER) | 位图信息头(BITMAPINFOHEADER) | 文件数据(DATA) |
(二)位图文件头(BITMAPFILEHEADER)
位图文件头(bitmap-file header)包含了图像类型、图像大小、图像数据存放地址和两个保留未使用的字段。
(1)BITMAPCOREHEADER
typedef struct tagBITMAPCOREHEADER // bmch{DWORD bcSize ; // size of the structure = 12WORD bcWidth ; // width of image in pixelsWORD bcHeight ; // height of image in pixelsWORD bcPlanes ; // = 1WORD bcBitCount ; // bits per pixel (1, 4, 8, or 24)}BITMAPCOREHEADER, * PBITMAPCOREHEADER ;
bfType 图片的类型 必须是BM 填0x4d42即十进制的19778
bfOffBits 从文件头开始到颜色数据结束的偏移量 54+sizeof(RGBQUAD)*256 bfSize 图片的大小,bfOffBits + 长 X 宽 X 位数例如:对于1*3 4位的图像 bfSize=1*3 + 54+sizeof(RGBQUAD)*16
其中,16是因为4位图像代表2的四次方,所以是16,再乘sizeof(RGBQUAD)
54是信息头的biSize+文件头的bfType
1和3是长和宽
bfReserved1和bfReserved1必须为0
(2)BITMAPV4HEADER
typedef struct{DWORD bV4Size ; // size of the structure = 120LONG bV4Width ; // width of the image in pixelsLONG bV4Height ; // height of the image in pixelsWORD bV4Planes ; // = 1WORD bV4BitCount ; // bits per pixel (1, 4, 8, 16, 24, or32)DWORD bV4Compression ; // compression codeDWORD bV4SizeImage ; // number of bytes in imageLONG bV4XPelsPerMeter ; // horizontal resolutionLONG bV4YPelsPerMeter ; // vertical resolutionDWORD bV4ClrUsed ; // number of colors usedDWORD bV4ClrImportant ; // number of important colorsDWORD bV4RedMask ; // Red color maskDWORD bV4GreenMask ; // Green color maskDWORD bV4BlueMask ; // Blue color maskDWORD bV4AlphaMask ; // Alpha maskDWORD bV4CSType ; // color space typeCIEXYZTRIPLE bV4Endpoints ; // XYZ valuesDWORD bV4GammaRed ; // Red gamma valueDWORD bV4GammaGreen ; // Green gamma valueDWORD bV4GammaBlue ; // Blue gamma value}BITMAPV4HEADER, * PBITMAPV4HEADER ;
(3)BITMAPV5HEADER
typedef struct{DWORD bV5Size ; // size of the structure = 120LONG bV5Width ; // width of the image in pixelsLONG bV5Height ; // height of the image in pixelsWORD bV5Planes ; // = 1WORD bV5BitCount ; // bits per pixel (1,4,8,16,24,or32)DWORD bV5Compression ; // compression codeDWORD bV5SizeImage ; // number of bytes in imageLONG bV5XPelsPerMeter ; // horizontal resolutionLONG bV5YPelsPerMeter ; // vertical resolutionDWORD bV5ClrUsed ; // number of colors usedDWORD bV5ClrImportant ; // number of important colorsDWORD bV5RedMask ; // Red color maskDWORD bV5GreenMask ; // Green color maskDWORD bV5BlueMask ; // Blue color maskDWORD bV5AlphaMask ; // Alpha maskDWORD bV5CSType ; // color space typeCIEXYZTRIPLE bV5Endpoints ; // XYZ valuesDWORD bV5GammaRed ; // Red gamma valueDWORD bV5GammaGreen ; // Green gamma valueDWORD bV5GammaBlue ; // Blue gamma valueDWORD bV5Intent ; // rendering intentDWORD bV5ProfileData ; // profile data or filenameDWORD bV5ProfileSize ; // size of embedded data or filenameDWORD bV5Reserved ;}BITMAPV5HEADER, * PBITMAPV5HEADER ;
可能有些读者看到这里有点乱,下面我来说一下这三者的关系:
BITMAPFILEHEADER是最早的版本
V4HEADER是windows95的拓展:
Windows 95 更改了一些原始 BITMAPINFOHEADER 栏位的定义。前 11 个栏位与 BITMAPINFOHEADER 结构中的相同,後 5 个栏位支援Windows 95 和 Windows NT 4.0 的图像颜色调配技术。除非使用 BITMAPV4HEADER结构的後四个栏位,否则您应该使用 BITMAPINFOHEADER(或 BITMAPV5HEADER)
这也就意味着:v4相当于是BITMAPFILEHEADER的一种拓展结构,如果不用那些拓展因素的话,是可以通用的,也就是可以用v4来接收FILEHEADER的数据,FILEHEADER相当于v4的子集
V5HEADER是windows98和2000的拓展:
基本原理同v4,有四个新栏位,只有其中三个有用。这些栏位支援 ICC Profile Format Specification
特别的是:BITMAPV5HEADER 的 bV5CSType 栏 位 能 拥 有 几 个 不 同 的 值 。
如 果 是LCS_CALIBRATED_RGB,那么它就与 BITMAPV4HEADER 结构相容。bV5Endpoints 栏位和伽马栏位必须有效。
如果 bV5CSType 栏位是 LCS_sRGB,就不用设定剩余的栏位。
如果 bV5CSType 栏位是 PROFILE_EMBEDDED,则 DIB 档案包含一个 ICC 设定档案。
如果栏位是 PROFILE_LINKED,DIB 档案就包含了 ICC 设定档案的完整路径和档案名称。
在上面最后两种情况下,bV5ProfileData 都是从 BITMAPV5HEADER 开始到设定档案资料或档案名称起始位置的偏移量。bV5ProfileSize 栏位给出了资
料或档案名的大小。不必设定 bV5Endpoints 和伽马栏位。(三)位图信息头
位图信息头(bitmap-information header)包含了位图信息头的大小、图像的宽高、图像的色深、压缩说明图像数据的大小和其他一些参数。
(1)BITMAPINFOHEADER
typedef struct tagBITMAPINFOHEADER // bmih{DWORD biSize ; // size of the structure = 40LONG biWidth ; // width of the image in pixelsLONG biHeight ; // height of the image in pixelsWORD biPlanes ; // = 1WORD biBitCount ; // bits per pixel (1, 4, 8, 16, 24, or 32)DWORD biCompression ; // compression codeDWORD biSizeImage ; // number of bytes in imageLONG biXPelsPerMeter ; // horizontal resolutionLONG biYPelsPerMeter ; // vertical resolutionDWORD biClrUsed ; // number of colors usedDWORD biClrImportant ; // number of important colors}BITMAPINFOHEADER, * PBITMAPINFOHEADER ;
biSize 本结构的大小,根据不同的操作系统而不同,在Windows中,此字段的值总为28h字节=40字节
biWidth BMP图像的宽度,单位像素
biHeight 总为0
biPlanes 总为0
biBitCount BMP图像的色深,即一个像素用多少位表示,常见有
1、4、8、16、24和32,分别对应单色、16色、256色、16位高彩色、24位真彩色和32位增强型真彩色
biCompression 压缩方式,0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定
biSizeImage BMP图像数据大小,必须是4的倍数,图像数据大小不是4的倍数时用0填充补足
biXPelsPerMeter 水平分辨率,单位像素/m
biYPelsPerMeter 垂直分辨率,单位像素/m
biClrUsed BMP图像使用的颜色,0表示使用全部颜色,对于256色位图来说,此值为100h=256
biClrImportant 重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,当显卡不能够显示所有颜色时,此值将辅助驱动程序显示颜色
(2)BITMAPCOREHEADER
“在 OS/2 样式的 DIB 内, BITMAPFILEHEADER 结构後紧跟了 BITMAPCOREHEADER结构” 也就意味着,在Windows中,BITMAPFILEHEADER后面,跟的是上面的 BITMAPINFOHEADER 而不是这个 BITMAPCOREHEADER,现在基本不怎么用这个了
typedef struct tagBITMAPCOREHEADER // bmch{DWORD bcSize ; // size of the structure = 12WORD bcWidth ; // width of image in pixelsWORD bcHeight ; // height of image in pixelsWORD bcPlanes ; // = 1WORD bcBitCount ; // bits per pixel (1, 4, 8, or 24)}BITMAPCOREHEADER, * PBITMAPCOREHEADER ;
BITMAPCOREHEADER 和 BITMAPINFOHEADER 两者的区别:
bcSize的值不同,BITMAPCOREHEADER中为40,而BITMAPCOREHEADER 中为12
(四)颜色表
彩色表/调色板(color table)是单色、16色和256色图像文件所特有的,相对应的调色板大小是2、16和256,调色板以4字节为单位,每4个字节存放一个颜色值,图像 的数据是指向调色板的索引。
而24色为真彩色,是没有调色盘,颜色数据直接存在位图数据中
以16色举例:将调色板想象成一个数组,每个数组元素的大小为4字节。
typedef struct{ unsigned char rgbBlue; //该颜色的蓝色分量 unsigned char rgbGreen; //该颜色的绿色分量 unsigned char rgbRed; //该颜色的红色分量 unsigned char rgbReserved; //保留值 } RgbQuad;
假设有一16色的BMP图像的调色板数据为:
调色板[0]=黑...调色板[9]=红、调色板[10]=绿…调色板[16]=白 |
对应代码:
RgbQuad color_table[16] = { // Blue Green Red Unused { 8, 8, 8, 0 }, //0 { 4, 100, 200, 0 }, //1 { 112, 128, 0, 0 }, //2 { 120, 120, 120, 0 }, //3 { 180, 160, 20, 0 }, //4 { 200, 176, 152, 0 }, //5 { 204, 204, 204, 0 }, //6 { 200, 192, 192, 0 }, //7 { 112, 112, 112, 0 }, //8 { 0, 0, 252, 0 }, //9 { 0, 248, 0, 0 }, //10 { 0, 248, 248, 0 }, //11 { 248, 0, 0, 0 }, //12 { 248, 0, 248, 0 }, //13 { 248, 248, 0, 0 }, //14 { 248, 248, 248, 0 } }; //15
(五)文件数据(data)
如果图像是单色、16色和256色,则紧跟着调色板的是位图数据,位图数据是指向调色板的索引序号。
如果位图是16位、24位和32位色,则图像文件中不保留调色板,即不存在调色板,图像的颜色直接在位图数据中给出。
16位图像使用2字节保存颜色值,常见有两种格式:5位红5位绿5位蓝和5位红6位绿5位蓝,即555格式和565格式。555格式只使用了15 位,最后一位保留,设为0。
24位图像使用3字节保存颜色值,每一个字节代表一种颜色,按红、绿、蓝排列。
32位图像使用4字节保存颜色值,每一个字节代表一种颜色,除了原来的红、绿、蓝,还有Alpha通道,即透明色。
如果图像带有调色板,则位图数据可以根据需要选择压缩与不压缩,如果选择压缩,则根据BMP图像是16色或256色,采用RLE4或RLE8压缩算 法压缩。
例:
这样一个1*5的五个红色像素的图片,对应的二进制代码为:
我们可以理解为:color_table[9]的值为红色,因此红色是16色调色盘的第10个元素,而0是第一个元素,对应的颜色表为白色,那么1*5为什么不是5个元素而是八个呢?这个我查了资料应该是:每行象素数是4的倍数,但是这里不知为何是8的倍数,默认补了3个空(如果有人清楚麻烦留言说下。。)。
要注意的是,颜色的二进制代码对应规则是:从左下角第一个元素开始对应的,自左向右,右侧没有像素时上移一行,再次左起
再比如: 和
两者对应的二进制代码均为:
那么,为何会这样呢?因为按上面的对应规则,两张图均是先读取8像素的红色数据,然后第一张图换到上一行读取最左侧蓝色,而第二张图继续读取8位蓝色,因此二者代码一样
如何区分呢?这就用到了上面的位图信息头,里面包含了图片的宽高,在绘制图片的时候如果宽度到头了,便会自动换行,绘制上一行。
(六)源代码
#include"stdafx.h"#include "stdio.h"#include "stdlib.h"#include "string.h"#includeusing namespace std;#define real_width 240#define real_height 320//#include "windef.h" //typedef unsigned char BYTE;typedef unsigned long DWORD; typedef unsigned short WORD;#pragma pack(2)typedef struct { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits;} BMPFILEHEADER_T;struct BMPFILEHEADER_S{ WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits;};typedef struct{ DWORD biSize; long biWidth; long biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; long biXPelsPerMeter; long biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant;} BMPINFOHEADER_T;typedef struct{ unsigned char rgbBlue; //该颜色的蓝色分量 unsigned char rgbGreen; //该颜色的绿色分量 unsigned char rgbRed; //该颜色的红色分量 unsigned char rgbReserved; //保留值 } RgbQuad;#pragma pack()void Create24(BYTE * pData, int width, int height, char * filename){ int size = width*height * 3; // 每个像素点3个字节 // 位图第一部分,文件信息 BMPFILEHEADER_T bfh; bfh.bfType = 0x4d42;//BM bfh.bfSize = size // data size + sizeof(BMPFILEHEADER_T) // first section size + sizeof(BMPINFOHEADER_T) // second section size ; bfh.bfReserved1 = 0; // reserved bfh.bfReserved2 = 0; // reserved bfh.bfOffBits = bfh.bfSize - size; // 位图第二部分,数据信息 BMPINFOHEADER_T bih; bih.biSize = sizeof(BMPINFOHEADER_T); bih.biWidth = width; bih.biHeight = height; bih.biPlanes = 1; bih.biBitCount = 24; bih.biCompression = 0; bih.biSizeImage = size; bih.biXPelsPerMeter = 0; bih.biYPelsPerMeter = 0; bih.biClrUsed = 0; bih.biClrImportant = 0; FILE * fp; fopen_s(&fp,filename, "wb"); if (!fp) return; fwrite(&bfh, 1, sizeof(BMPFILEHEADER_T), fp); fwrite(&bih, 1, sizeof(BMPINFOHEADER_T), fp); fwrite(pData, 1, size, fp); fclose(fp);}void Create4(BYTE * pData, int width, int height, char * filename){ int size = width*height ; // 每个像素点2个字节 // 位图第一部分,文件信息 BMPFILEHEADER_T bfh; bfh.bfType = 0x4d42;//BM bfh.bfSize = sizeof(RgbQuad)*16 //一共16种颜色,一个颜色4字节 + sizeof(BMPFILEHEADER_T) // first section size + sizeof(BMPINFOHEADER_T) // second section size +size; bfh.bfReserved1 = 0; // reserved bfh.bfReserved2 = 0; // reserved bfh.bfOffBits = bfh.bfSize - size; // 位图第二部分,数据信息 BMPINFOHEADER_T bih; bih.biSize = sizeof(BMPINFOHEADER_T); bih.biWidth = width; bih.biHeight = height; bih.biPlanes = 1; bih.biBitCount = 4; bih.biCompression = 0; bih.biSizeImage = size; bih.biXPelsPerMeter = 0; bih.biYPelsPerMeter = 0; bih.biClrUsed = 0; bih.biClrImportant = 0; //位图第三部分,调色盘 RgbQuad color_table[16] = { // Blue Green Red Unused { 8, 8, 8, 0 }, //0 { 4, 100, 200, 0 }, //1 { 112, 128, 0, 0 }, //2 { 120, 120, 120, 0 }, //3 { 180, 160, 20, 0 }, //4 { 200, 176, 152, 0 }, //5 { 204, 204, 204, 0 }, //6 { 200, 192, 192, 0 }, //7 { 112, 112, 112, 0 }, //8 { 0, 0, 252, 0 }, //9 { 0, 248, 0, 0 }, //10 { 0, 248, 248, 0 }, //11 { 248, 0, 0, 0 }, //12 { 248, 0, 248, 0 }, //13 { 248, 248, 0, 0 }, //14 { 248, 248, 248, 0 } }; //15 FILE * fp; fopen_s(&fp, filename, "wb"); if (!fp) return; fwrite(&bfh, 1, sizeof(BMPFILEHEADER_T), fp); fwrite(&bih, 1, sizeof(BMPINFOHEADER_T), fp); fwrite(&color_table, 1, sizeof(RgbQuad) * 16, fp); fwrite(pData, 1, size, fp); fclose(fp);}void main(){ int i = 0, j = 0; int chose ; cout << "请输入新位图的位深(目前能输入4或24)" << endl; cin >> chose; switch (chose) { case 24: struct { BYTE b; BYTE g; BYTE r; } pRGB[240][320]; // 定义位图数据 memset(pRGB, 255, sizeof(pRGB)); // 设置背景为黑色 // 在中间画一个100*100的矩形 for (i = 70; i<170; i++){ for (j = 110; j<210; j++){ pRGB[i][j].g = 255; pRGB[i][j].r = 255; pRGB[i][j].b = 0; } } // 生成BMP图片 Create24((BYTE*)pRGB, real_height, real_width, "f:\\bmp-24.bmp"); i = 0, j = 0; cout << "创建24-bit成功" << endl; break; case 4: BYTE data[real_height][real_width]; memset(data, 255,sizeof(data)); for (i = 30; i<130; i++){ for (j = 20; j<100; j++){ data[i][j] = 0x9; } } Create4((BYTE*)data, real_width, real_height, "f:\\bmp-4.bmp"); cout << "创建4-bit成功" << endl; break; default: break; } }
注意事项:
(1)对齐格式:开头结构体前后要加上#pragma pack(2)和#pragma pack(),作用是取消自动对齐,否则结构体大小不一样。
(2)使用方式:在控制台输入4或24进行选择,分别在f盘生成16色或24色的图片