《簡介》
點陣圖(bitmap)格式是 Windows 採用的圖像檔案儲存格式,在 Windows 環境下運行的所有圖像處理軟件都支持這種格式。Windows 3.0 以前的 BMP 格式與顯示設備有關,因此稱為設備相關點陣圖(Device-Dependent Bitmap, DDB)格式。Windows 3.0 以後的 BMP 格式則與顯示設備無關(Device-Independent Bitmap, DIB),目的是為了讓 Windows 能夠在任何類型的顯示設備上顯示點陣圖檔案。點陣圖檔案的預設副檔名是 BMP 或 bmp。
*******************************************
《點陣圖檔案結構》
點陣圖檔案由四個部份組成:
- Bitmap File Header
- Bitmap Info Header
- Color Table (Palette)
- Bitmap Array
Note: 以下欄位資料皆為 little-endian! Little-Endian 的意思是:若某個欄位值為 0x1234,當你將BMP檔案用 UltraEdit 之類的純文字編輯器打開時,則你看到的值會是 0x3412,這是 Intel 制定的儲存方式,把值小的位元組(0x34)存在前面;詳情可參考 Big Endian 和 Little Endian 架構的說明
Shift | Name | Size (bytes) |
Content | |
Bitmap File Header |
0000h | Identifier (ID) | 2 | 'BM'【註1】 |
0002h | File Size | 4 | 整個點陣圖檔案的大小(單位:byte) | |
0006h | Reserved | 4 | 保留欄位 | |
000Ah | Bitmap Data Offset | 4 | 點陣圖資料開始之前的偏移量(單位:byte) | |
Bitmap Info Header |
000Eh | Bitmap Header Size | 4 | Bitmap Info Header 的長度【註2】 |
0012h | Width | 4 | 點陣圖的寬度,以像素(pixel)為單位 | |
0016h | Height | 4 | 點陣圖的高度,以像素(pixel)為單位【註3】 | |
001Ah | Planes | 2 | 點陣圖的位元圖層數【註4】 | |
001Ch | Bits Per Pixel | 2 | 每個像素的位元數 1:單色點陣圖(使用 2 色調色盤) 4:4 位元點陣圖(使用 16 色調色盤) 8:8 位元點陣圖(使用 256 色調色盤) 16:16 位元高彩點陣圖(不一定使用調色盤) 24:24 位元全彩點陣圖(不使用調色盤) 32:32 位元全彩點陣圖(不一定使用調色盤) 【註5】 |
|
001Eh | Compression | 4 | 壓縮方式【註6】: 0:未壓縮 1:RLE 8-bit/pixel 2:RLE 4-bit/pixel 3:Bitfields |
|
0022h | Bitmap Data Size | 4 | 點陣圖資料的大小(單位:byte)【註7】。 | |
0026h | H-Resolution | 4 | 水平解析度(單位:像素/公尺)【註8】 | |
002Ah | V-Resolution | 4 | 垂直解析度(單位:像素/公尺) | |
002Eh | Used Colors | 4 | 點陣圖使用的調色盤顏色數【註9】 | |
0032h | Important Colors | 4 | 重要的顏色數【註10】 | |
Palette | 0036h | Palette | N*4 | 調色盤資料。 每個索引值指定一種顏色:0x00RRGGBB 其中最高位元組保留為零 |
Bitmap Array |
- | Bitmap Data | - | 點陣圖資料【註11】 |
【註1】此欄原本有多種識別碼,用來識別點陣圖的類型:
'BM' - Windows 3.1x, 95, NT, ...
'BA' - OS/2 Bitmap Array
'CI' - OS/2 Color Icon
'CP' - OS/2 Color Pointer
'IC' - OS/2 Icon
'PT' - OS/2 Pointer
不過既然 OS/2 並不普及,目前皆在 Windows 上作業,因此 ID 全都是 'BM'。
【註2】此欄原本有多種數值,依作業系統種類而定:
28h - Windows 3.1x, 95, NT, ...
0Ch - OS/2 1.x
F0h - OS/2 2.x
以目前 Windows 常用的點陣圖來說,此欄位數值通常是 28h。
但因為微軟已經制定出了新的點陣圖格式,其中的 Bitmap Info Header 結構變化較大,長度加長,所以最好不要直接使用常數 28h,而是應該從實際檔案中讀取這個數值,才能確保程式相容性。
【註3】高度可能為負值,負值表示掃瞄方向由上而下。
但若高度是負值時,此點陣圖將不能被壓縮!(也就是說 Compression 欄位總是為 0)
【註5】16 及 32 位元點陣圖是否使用調色盤必須由 Compression 欄位的數值決定,
請參考 Bitfields 的解說。
【註6】點陣圖壓縮方式有以下四種:BI_RGB,BI_RLE8,BI_RLE4,以及BI_BITFIELDS。
- None (BI_RGB)
表示此點陣圖資料沒有壓縮,不使用調色盤。
- RLE 8bpp (BI_RLE8)
每個像素為 8 bit 的 RLE 壓縮編碼(Run-Length Encoding)(總共可使用 256 色)。
有「編碼模式」(Encoded Mode)和「絕對模式」(Absolute Mode)兩種方法,
可在同一幅圖檔中的任何地方交錯使用。
「編碼模式」:由兩個位元組(byte)組成。
第一個位元組指定 "Length"(使用相同顏色的像素數目),
第二個位元組指定 "Run"(此像素使用的調色盤索引)。
若第一個位元組為零,則表示特殊意義:
0x0000 - 表示此列結束
0x0001 - 表示此點陣圖檔案結束
0x0002 - 表示其後的兩位元組分別表示
下個像素位置與目前像素位置的水平/垂直偏移量。
0x000x - 表示絕對模式。
「絕對模式」:第一個位元組為 0x00,第二個位元組為 0x03 ~ 0xFF 之間的數值。
其中第二個位元組表示後續資料的長度(單位為 byte),
後續資料的每個位元組都表示單一像素的調色盤索引值。
每一模式的編碼長度都必須與字邊界對齊(word-aligned),也就是 2 的倍數。
使用「編碼模式」時,由於每組編碼皆為兩個位元組,所以毋需多加處理;
但是使用「絕對模式」時,則必須在最後補上適當的 0x00 以使資料長度對齊 2 的倍數。
下面是一個 BI_RLE8 的例子:
03 01 05 02 00 03 00 01 02 00 02 01 00 02 05 01 02 03 00 00 09 02 00 01
這些資料可解讀為:
壓縮資料 解壓縮之後的資料 03 01 01 01 01 05 02 02 02 02 02 02 00 03 00 01 02 00 00 01 02(最後的 00 是為了 word-aligned 才補上的) 02 01 01 01 00 02 05 01 從目前位置右移 5 像素之後,向下移一列 02 03 03 03 00 00 此列結束 09 02 02 02 02 02 02 02 02 02 02 00 01 此點陣圖結束 上述資料的圖形如下:
- RLE 4bpp (BI_RLE4)
每個像素為 4-bit 的 RLE 壓縮編碼(總共可使用 16 色)。
同樣也有「編碼模式」與「絕對模式」,且可以在同一圖檔中任何地方使用任一模式。
「編碼模式」:由兩個位元組組成。
第一個位元組指定像素數目,
第二個位元組包含兩個調色盤索引:
第一個像素使用高 4 位元的索引值、
第二個像素使用低 4 位元的索引值、
第三個像素又使用高 4 位元的索引值,以此類推。
若第一個位元組為零,則表示特殊意義:
0x0000 - 表示此列結束
0x0001 - 表示此點陣圖檔案結束
0x0002 - 表示其後的兩位元組分別表示
下個像素位置與目前像素位置的水平/垂直偏移量。
0x000x - 表示絕對模式。
「絕對模式」:第一個位元組為 0x00,第二個位元組表示後續資料的長度(單位為 byte)。
後續資料的每個位元組都含有兩個調色盤索引值(高 4 及低 4 位元),
每個索引值對應一個像素。
每一模式的編碼長度仍然必須與字邊界對齊(word-aligned)。
使用「編碼模式」時,由於每組編碼皆為兩個位元組,所以毋需多加處理;
但是使用「絕對模式」時,則必須在最後補上適當的 0 以使資料長度對齊 2 的倍數。
下面是一個 BI_RLE4 的例子:
03 11 05 02 00 05 01 23 10 00 02 22 00 02 05 01 02 13 00 00 09 10 00 01
這些資料可解讀為:
壓縮資料 解壓縮之後的資料 03 11 1 1 1 05 02 0 2 0 2 0 00 05 01 23 10 00 0 1 2 3 1(最後的 0 00 是為了 word-aligned 才補上的) 02 22 2 2 00 02 05 01 從目前位置右移 5 像素之後,向下移一列 02 13 1 3 00 00 此列結束 09 10 1 0 1 0 1 0 1 0 1 00 01 此點陣圖結束 上述資料的圖形如下:
- Bitfileds (BI_BITFIELDS)
只有當「Bit Per Pixel」欄位的數值為 16 或 32 時,才會使用 BI_BITFIELDS 這種格式。
使用 BI_BITFIELDS 時,點陣圖檔中原本的調色盤位置會被三個 DWORD 佔據,
分別代表 R、G、B 三個顏色分量的遮罩(mask)。
例如:- Bit_Per_Pixel = 16, Compression = BI_RGB(無壓縮),
則每個像素的 16 位元之中:
最低 5 位元表示藍色分量,
中間 5 位元表示綠色分量,
接著的高 5 位元則是紅色分量,
最高的 1 位元保留不使用。
此格式即為 RGB555 格式。 - Bit_Per_Pixel = 16, Compression = BI_BITFIELDS,
紅、綠、藍的 mask 分別為 0xF800, 0x07E0, 0x001F,
則每個像素的 16 位元之中:
最低 5 位元表示藍色分量,
中間 6 位元表示綠色分量,
最高 5 位元則是紅色分量,
此格式即為 RGB565 格式。
- Bit_Per_Pixel = 16, Compression = BI_RGB(無壓縮),
【註7】若沒有壓縮(Compression 欄位為 0),則此欄數值可設為 0。
(我發現許多繪圖軟體根本不看此欄數值,隨便填寫也無所謂,圖檔仍可正常開啟)
【註8】若要換算為 dpi,則將此欄數值要除以39.37(吋/公尺)
例如,此欄數值若為 2834 (pixels per meter),
則 2834 ÷ 39.37 = 72 (pixels per inch) = 72 dpi
【註9】此欄表示圖檔實際使用的顏色數目,若數值為 0,表示使用所有調色盤顏色。
如果此欄數值並非「可用顏色的最大值」或者「零」,則需注意調色盤尺寸的計算。
例如,在 4 bpp bitmap 中,調色盤預設尺寸應是 16*4 bytes,
但若 Used Color 欄位數值並非 16 或 0,則調色盤尺寸應是 Used_Color_Number * 4 (bytes)。
【註10】當此欄的值等於「顏色數」或者為 0 時,表示所有顏色都一樣重要。
【註11】緊跟在調色盤之後的就是點陣圖資料陣列。
每一掃描列的長度取決於圖檔的寬度及顏色深度(Color Depth),
但是每一掃描列的長度必需是 4 byte 的倍數(DWORD-aligned)!
正常的點陣圖掃描列是由底向上儲存的:
陣列中的第一個 byte 表示全圖左下角的像素,而最後一個 byte 則表示全圖右上角的像素。
但如果是正向掃描(Height 欄位為負值),則掃描方向則是由上而下。
C語言讀寫BMP影像檔
[Introduction]
撰寫視窗軟體時應該是沒有如此的煩惱,因為bcb, vc++都有提供視覺化元件可以使用,但是對於開發演算法以及產生test pattern時根本不需要圖形化介面,或者是嵌入式系統在開發時需要使用semihosting輸出影像時,這時要開圖檔或者寫圖檔都是件麻煩事。而 bmp是一個最簡單的泛用圖檔格式,但是又不須像raw一樣,需要設定長寬大小,只要點兩下馬上看得到結果。對於嵌入式系統或者vlsi相關的設計,有很 大的幫助。因此這邊簡單寫一個讀寫bmp的函式。
[Article]
BMP檔案格式,由bmpheader與image array兩個部份組成。
bmp header通常長度為58byte,所以也可以值些忽略前面的部份,讀取影像的資料,但是為了寫回電腦時還是一樣是bmp檔,所以還是需要把header記下來,先宣告一個結構(struct)來存放header資料,
typedef struct _lbheader{ unsigned short identifier; // 0x0000 unsigned int filesize; // 0x0002 unsigned int reserved; // 0x0006 unsigned int bitmap_dataoffset; // 0x000A unsigned int bitmap_headersize; // 0x000E unsigned int width; // 0x0012 unsigned int height; // 0x0016 unsigned short planes; // 0x001A unsigned short bits_perpixel; // 0x001C unsigned int compression; // 0x001E unsigned int bitmap_datasize; // 0x0022 unsigned int hresolution; // 0x0026 unsigned int vresolution; // 0x002A unsigned int usedcolors; // 0x002E unsigned int importantcolors; // 0x0032 unsigned int palette; // 0x0036 } __attribute__((packed,aligned(1))) lbheader;
記住gcc有資料格式對齊的考量,為了到時後讀檔方便,所以加入__attribute__((packed,aligned(1))),強制gcc把結構作緊密排列,這樣是為了到時候讀取資料時可以直接使用指標來擺放資料,就不需要一筆一筆去指定資料。
lbheader hbmp; unsigned char* ptr; ptr = (unsigned char *)&hbmp; fread(ptr, sizeof(unsigned char), sizeof(lbheader), fp);
像這樣開始讀取檔案的58byte,直接使用unsigned char型態指標從結構開始位址開始寫入,數值就會按照位置擺入結構中。
讀完header後,取出width與height,由於一個pixel有rgb三個pixel,算影像大小的公式為width*height*3,再與(filesize-bitmap_dataoffset)比較看看是否相等,判斷大小是否有算錯,
下面是讀取bmp的函式
int readbmp(char* filename, lbheader& hbmp, int mode, unsigned char* buffer) { FILE* ifp; char c[128]; unsigned char* ptr; sprintf(c, "./result/%s.bmp", filename); ifp = fopen(c, "rb"); if(ifp==NULL){ printf("readbmp: file open error\n"); return -1; } ptr = (unsigned char *)&hbmp; fread(ptr, sizeof(unsigned char), sizeof(lbheader), ifp); if(mode==1){ fread(buffer, sizeof(unsigned char), (hbmp.width*hbmp.height*3), ifp); } else{ fclose(ifp); return (hbmp.width*hbmp.height*3); } fclose(ifp); return 1; }
要讀取影像時要先知道影像大小,以便宣告足夠的記憶體空間,所以要呼叫兩次readbmp(),一次先取到header,第二次才真的取影像資料,
// 先宣告指標與結構 unsigned char* bimage; lbheader bmpinfo; // 取得適當大小的陣列 bimage = (unsigned char *)malloc(sizeof(unsigned char)*readbmp("2", bmpinfo, 0, bimage)); // 讀取影像 readbmp("2", bmpinfo, 1, bimage);
寫入bmp的function。
int writebmp(char* filename, lbheader hbmp, unsigned char* buffer) { FILE* ofp; char c[128]; unsigned char* ptr; sprintf(c, "./result/%s.bmp", filename); ofp = fopen(c, "wb"); if(ofp==NULL){ printf("writebmp: file open error\n"); return -1; } ptr = (unsigned char *)&hbmp; fwrite(ptr, sizeof(unsigned char), sizeof(lbheader), ofp); fwrite(buffer, sizeof(unsigned char), (hbmp.width*hbmp.height*3), ofp); fclose(ofp); return 1; }
但是要注意一點,BMP擺放影像是上下顛倒所以要記得將影像轉回來,不然存出來的圖會是顛倒的。
參考網址
http://crazycat1130.pixnet.net/blog/post/1345538#mark-10
http://capricorn-liver.blogspot.tw/2010/11/cbmp.html
留言列表