網頁

2010年11月9日 星期二

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擺放影像是上下顛倒所以要記得將影像轉回來,不然存出來的圖會是顛倒的。

沒有留言:

張貼留言