2017年6月21日 星期三

[原始碼] C/C++ 讀取或修改 BMP 圖檔 (格式檔頭解析)

[源代碼] C/C++ 如何讀取修改 BMP 檔案 (格式檔頭解析)

C/C++ 純軟體讀取修改 bitmap 檔案完整介紹如下,文末附上詳細的原始碼可以參考,例外也有如何讀取灰階圖gray與彩圖RGB的問題、以及解析度不為4倍數的時候如何處理4byte位元對齊,都有講到並且寫成一個萬用的函式、源代碼(souce code)。

介紹

BMP圖檔大致分為四個部分
  • 檔案檔頭(14byte)
  • 圖片檔頭(40byte)
  • 調色盤(1024byte)
  • Raw檔
這裡的byte看成是一個字元(char),如果是字元與Raw檔讀取就順著讀而已,比較特別是數字讀取的時候要一起讀;比如說根據表頭某4byte是一組的他們是 00 FF 00 00 那你要反著讀變成 0000FF00 -> FF00 而不是直接讀取。
有興趣的可以查查這個詞 Endianness:https://goo.gl/xu9uYB

檔案檔頭(14byte)

範例代碼:(占用位元可以從代碼上看到)
struct BmpFileHeader{
    uint16_t bfTybe = 0x424D;
    uint32_t bfSize;
    uint16_t bfReserved1 = 0;
    uint16_t bfReserved2 = 0;
    uint32_t bfOffBits = 54;
};
其中有寫數值的地方是類內初始化(C++11標準才可以使用),有寫的地方代表幾乎就那樣了,通常不會動到,只要補空白的地方就好。
第一個比較特別的是他是BM,你也可以寫成 unsigned char type[2]= {'B', 'M'};,他就是固定的識別代碼,標準規定。
再來size是只檔案大小,就是包含上述全部資料大小,這也會是你直接對BMP右鍵內容看到的容量大小。
再來最後面的54是固定的就是14+40,不過如果你是用灰階圖記得要加上1024(調色盤占用的)。

圖片檔頭(40byte)

範例代碼:(占用位元可以從代碼上看到)
struct BmpInfoHeader{
    uint32_t biSize = 40;
    uint32_t biWidth;
    uint32_t biHeight;
    uint16_t biPlanes = 1; // 1=defeaul, 0=custom
    uint16_t biBitCount;
    uint32_t biCompression = 0;
    uint32_t biSizeImage = 0;
    uint32_t biXPelsPerMeter = 0; // 72dpi=2835, 96dpi=3780
    uint32_t biYPelsPerMeter = 0; // 120dpi=4724, 300dpi=11811
    uint32_t biClrUsed = 0;
    uint32_t biClrImportant = 0;
};
size檔頭的大小
height width 圖片的長與寬
bit 位元數,彩圖要設RGB 8*3=24,灰階圖設8
imagesize 指的是 長*寬,如果是彩圖有RGB還要再 *3
biClrUsed 指的是調色盤的顏色數,彩圖RGB因為不用自己寫調色盤用系統的所以設0,灰階圖擇要自訂調色盤這裡要設256
PelsPerMeter指的是密度,一般軟體好像也不看都是設0,windwos電腦預設是96dpi,大概就差別在你把這張圖拉上PPT上設越高會縮的越小。
imagesize 有些軟體存出來的bmp會是0,最好不要取這個值用長x寬自己算

調色盤(1024byte)

這個是可選的,如果圖片來源是 RGB 24bit 的圖就可以留空不管他,如過圖片是灰階圖你就要自己補上調色盤,調色盤意思其實也很簡單就是 00 00 00 00~ FF FF FF 00 而已,這是因為灰階圖只需用一個byte表示,但是整張BMP需要用 RGB 表示,所以調色盤的功用就是把看到的RAW檔,就比如看到 00 變成 00 00 00 看到 FF 變成 FF FF FF
你可以一次做好整份調色盤在寫入,也可以用for迴圈寫入,數量不多不會太損效能可以用for比較整潔些,大致如下寫法。
for(unsigned i = 0; i < 256; ++i)
    img << uch(i) << uch(i) << uch(i) << uch(0);

Raw檔

比較特別的是讀取的方式是從圖的左下角開始往右邊上面讀取,而且數據是BGR反過來的,並且他們的行會對齊4byte,如果圖片是40x42那麼你寫入的時候要寫成40x44不足的2位元補空白0。
bmp 4bit 對齊的公式代碼如下
size_t RealWidth = Width * bits/8;
size_t alig = (RealWidth*3)%4;
算出來是2的話就是說每行的結尾要塞 2 個 char(0) 就是 0x0000
算出來是4的話就是說每行的結尾要塞 3 個 char(0) 就是 0x000000
值得注意的是公式要針對不同的位元的圖片做不同的處理
可以想像成假設同一張3x3的圖片那麼對黑白圖來說就是要補1個空白,但是對於彩色的圖來說每個點個別有RGB,也就是總共是9個點,9個點的話4位元對齊就需要補3個空白。
總結一下萬用公式就是 實際寬度*3 % 4 這樣。

注意

灰階圖要多寫調色盤,有三個地方要更動
FileHeader 的 size        +1024
FileHeader 的 headSize    +1024
InfoHeader 的 size        +1024
InfoHeader 的 ncolours    =256
除此之外也記得讀檔的時候檔案指標要跳過調色盤(建議是直接抓headSize比較省事)。
讀寫RAW檔區域的時候,記得跳過4byte對齊,對齊算法是
size_t alig = ((width*bits/8)*3) % 4;

範例 SouceCode

沒有留言:

張貼留言