首先,本篇文章參考了眾多博客,有:
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 structure RGBQUAD 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 = 12 WORD bcWidth ; // width of image in pixels WORD bcHeight ; // height of image in pixels WORD bcPlanes ; // = 1 WORD 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 = 120 LONG bV4Width ; // width of the image in pixels LONG bV4Height ; // height of the image in pixels WORD bV4Planes ; // = 1 WORD bV4BitCount ; // bits per pixel (1, 4, 8, 16, 24, or 32) DWORD bV4Compression ; // compression code DWORD bV4SizeImage ; // number of bytes in image LONG bV4XPelsPerMeter ; // horizontal resolution LONG bV4YPelsPerMeter ; // vertical resolution DWORD bV4ClrUsed ; // number of colors used DWORD bV4ClrImportant ; // number of important colors DWORD bV4RedMask ; // Red color mask DWORD bV4GreenMask ; // Green color mask DWORD bV4BlueMask ; // Blue color mask DWORD bV4AlphaMask ; // Alpha mask DWORD bV4CSType ; // color space type CIEXYZTRIPLE bV4Endpoints ; // XYZ values DWORD bV4GammaRed ; // Red gamma value DWORD bV4GammaGreen ; // Green gamma value DWORD bV4GammaBlue ; // Blue gamma value } BITMAPV4HEADER, * PBITMAPV4HEADER ;
(3)BITMAPV5HEADER
typedef struct { DWORD bV5Size ; // size of the structure = 120 LONG bV5Width ; // width of the image in pixels LONG bV5Height ; // height of the image in pixels WORD bV5Planes ; // = 1 WORD bV5BitCount ; // bits per pixel (1,4,8,16,24,or32) DWORD bV5Compression ; // compression code DWORD bV5SizeImage ; // number of bytes in image LONG bV5XPelsPerMeter ; // horizontal resolution LONG bV5YPelsPerMeter ; // vertical resolution DWORD bV5ClrUsed ; // number of colors used DWORD bV5ClrImportant ; // number of important colors DWORD bV5RedMask ; // Red color mask DWORD bV5GreenMask ; // Green color mask DWORD bV5BlueMask ; // Blue color mask DWORD bV5AlphaMask ; // Alpha mask DWORD bV5CSType ; // color space type CIEXYZTRIPLE bV5Endpoints ; // XYZ values DWORD bV5GammaRed ; // Red gamma value DWORD bV5GammaGreen ; // Green gamma value DWORD bV5GammaBlue ; // Blue gamma value DWORD bV5Intent ; // rendering intent DWORD bV5ProfileData ; // profile data or filename DWORD bV5ProfileSize ; // size of embedded data or filename DWORD 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 = 40 LONG biWidth ; // width of the image in pixels LONG biHeight ; // height of the image in pixels WORD biPlanes ; // = 1 WORD biBitCount ; // bits per pixel (1, 4, 8, 16, 24, or 32) DWORD biCompression ; // compression code DWORD biSizeImage ; // number of bytes in image LONG biXPelsPerMeter ; // horizontal resolution LONG biYPelsPerMeter ; // vertical resolution DWORD biClrUsed ; // number of colors used DWORD 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 = 12 WORD bcWidth ; // width of image in pixels WORD bcHeight ; // height of image in pixels WORD bcPlanes ; // = 1 WORD 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" #include <iostream> using 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色的圖片