《簡介》

點陣圖(bitmap)格式是 Windows 採用的圖像檔案儲存格式,在 Windows 環境下運行的所有圖像處理軟件都支持這種格式。Windows 3.0 以前的 BMP 格式與顯示設備有關,因此稱為設備相關點陣圖(Device-Dependent Bitmap, DDB)格式。Windows 3.0 以後的 BMP 格式則與顯示設備無關(Device-Independent Bitmap, DIB),目的是為了讓 Windows 能夠在任何類型的顯示設備上顯示點陣圖檔案。點陣圖檔案的預設副檔名是 BMP 或 bmp。

*******************************************
《點陣圖檔案結構》

點陣圖檔案由四個部份組成:

  1. Bitmap File Header
  2. Bitmap Info Header
  3. Color Table (Palette)
  4. 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)

【註4】未知的功能...永遠被設為 1

【註5】16 及 32 位元點陣圖是否使用調色盤必須由 Compression 欄位的數值決定
    請參考 Bitfields 的解說。

【註6】點陣圖壓縮方式有以下四種:BI_RGBBI_RLE8BI_RLE4,以及BI_BITFIELDS

  1. None (BI_RGB)
    表示此點陣圖資料沒有壓縮,不使用調色盤。

  2. 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 此點陣圖結束

    上述資料的圖形如下:

  3. 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 此點陣圖結束

    上述資料的圖形如下:

  4. Bitfileds (BI_BITFIELDS)
    只有當「Bit Per Pixel」欄位的數值為 16 或 32 時,才會使用 BI_BITFIELDS 這種格式。
    使用 BI_BITFIELDS 時,點陣圖檔中原本的調色盤位置會被三個 DWORD 佔據,
    分別代表 R、G、B 三個顏色分量的遮罩(mask)。
    例如:
    1. Bit_Per_Pixel = 16, Compression = BI_RGB(無壓縮),
      則每個像素的 16 位元之中:
      最低 5 位元表示藍色分量,
      中間 5 位元表示綠色分量,
      接著的高 5 位元則是紅色分量,
      最高的 1 位元保留不使用。
      此格式即為 RGB555 格式。
    2. Bit_Per_Pixel = 16, Compression = BI_BITFIELDS,
      紅、綠、藍的 mask 分別為 0xF800, 0x07E0, 0x001F,
      則每個像素的 16 位元之中:
      最低 5 位元表示藍色分量,
      中間 6 位元表示綠色分量,
      最高 5 位元則是紅色分量,
      此格式即為 RGB565 格式。

【註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

立你斯 發表在 痞客邦 留言(0) 人氣()