你所能用到的BMP格式介紹(一)


這些說明是我擔任學校多媒體技術助教自己編寫的實驗說明,嘔心瀝血結合C++詳細介紹BMP格式。 

原理篇:

一、編碼的意義。

       讓我們從一個簡單的問題開始,-2&-255(中間的操作符表示and的意思)的結果是多少,這個很簡單的問題,但是能夠寫出解答過程的人並不 多。這個看起來和圖片格式沒有關系的問題恰恰是圖片格式的核心內容以至於整個計算機系統的核心內容,多媒體技術雖然沒有數據結構,操作系統等計算機基礎課 所占的地位重,但是在於研究編碼方面有着非常重要的地位。圖像其實可以看做一種特殊編碼過的文件。

二、從簡單的24位bmp開始

       bmp是最常見也是編碼方式最簡單的圖片格式,這里不說明一幅圖片是怎么顯示在電腦上的,那不是多媒體技術研究的問題,我們來研究bmp的格式問題,為了使各位能夠最快的了解bmp格式,我們從24位的一個16*16的小圖像開始。

       我們使用常用的繪圖軟件創建一個16*16的24位bmp圖像,如下圖所示:

                                                                       

      可以看到圖片很小,我們使用ultra-edit看看其內部是什么(ultra-edit是一個比記事本更加高級的編輯軟件,可以在網上下載到),我們打開其內部看到的是如下的一個十六進制的數據文件:

      

     

        看起來很高深而又很凌亂的樣子,我們慢慢地說明這些看起來很凌亂的數據流都代表了什么意思,首先我們要說明的是,這里面一個數字代表的是一個字節,比如頭兩個數42 4d是兩個十六進制的數,代表了兩個字節。可以看到在UE中一行是十六個字節。

        在具體說明每個字節的含義之前,首先需要說明的是字節的排布方式,在操作系統和計算機組成結構里面有大端法和小端法(如果有遺忘可以查一下書),簡易的說 法是這樣的,小端法的意思是“低地址村存放低位數據,高地址存放高位數據”,大端法就是反過來的,舉個例子,如果地址從左到右依次增大,那么數據01 02的小端法存儲方式是02 01,大端法的存儲方式就是01 02。在所有的intel的機器上都是采用的小端法,而大端法主要存在於摩托羅拉造的處理器的機器上,所以如果你用的是一個果粉,用的是MAC的話,那么 你看到的數據排布方式是和我們說明中是相反的。

         下面我們來依次說明每個字節的含義:

          字節0-1:42 4d 轉換成ASCII碼就是BM (如果不知道什么是ASCII碼可以查閱網上相關資料,在圖像中,這種編碼是很常見的,所以弄懂一個圖像的格式可以學會很多編碼的知識),這兩個字節表示 的是一種標示,也就是當計算機把這個圖片文件加載到內存中時,從第一個字節開始讀取,讀到頭兩個字節是BM, 那么計算機就知道了,這個文件是一個bmp圖像文件。

         字節2-5:注意這里是小端法,雖然我們看到的數據形式是36 03 00 00,但是實際的數值是0x00000336(0x表示十六進制),轉換成十進制是822,這四個字節表示的是圖像的大小,對比一下我們的圖像,可以發現和圖像的大小是一樣的。

        

     

       字節6-9:這四個0是保留的,因為最開始給計算機文件等等設定標准的是一幫數學家,所以,從這里的保留字符可以看到數學家獨特的嚴謹,保留翻譯成白話文就是暫時沒有作用,以后留着擴展。

        字節A-D:這四個字節十分重要,這個表示圖像數據區的偏移,當你寫程序需要找到圖像數據區時就需要這個字段的值,在后面我們還可以對其進行驗證,這里的值轉換成十進制是54。

       以上的14個字節被稱作bmp文件頭,顧名思義,就是介紹bmp文件的基本信息的。

       接下的字節:

       字節0E-11:這四個字節的十六進制數,轉換成十進制就是40,這個的含義表示位圖信息的版本,具體有哪些版本,因為和我們的實驗無關,我們不進行說明,只要將這個值固定為40就可以了。

       字節12-15:10 00 00 00 ,也就是0x10,轉換成十進制是16,表示圖像的寬度,當然單位是像素。

       字節16-19:10 00 00 00 ,也就是0x10,轉換成十進制是16,表示圖像的高度,當然單位是像素。不過注意,高度有可能是負的,為什么,在下面位圖數據部分還會說明。可以看到這兩個信息和上圖windows系統內顯示的也是一致的。

       字節1A-1B:永遠為1,規定如此。

       字節1C-1F:取出來是0x18,十進制24,表示每個像素占24個位,也就是3 Byte,對於24位的位圖,明顯這是正確的。

       字節1E-21:這四個字節是0,表示使用BI_RGB,這個縮寫的意思代表的是不壓縮。關於壓縮在最后的附件部分說明。

       字節22-25:這四個字節表示圖像大小,也就是圖像數據的大小,去掉這些信息頭,文件頭和后面要說的調色板的大小。這里是0x300,我們這樣驗證這個 數據的正確性,在字節2-5中我們得到的圖像大小為0x336,在A-D中偏移量為0x36,也就是前面的所有輔助信息的量為0x36,兩個相減,剛好得 到0x300,也就是圖像數據的大小。可以看到和取出來的大小是一致的。

       字節26-29:水平分辨率,這個主要是打印用,可以不管,就用這個值就行了,基本上不會變。

       字節2A-2D:垂直分辨率,這個主要是打印用,可以不管,就用這個值就行了,基本上不會變。

       字節2E-32:這里表示實際使用的顏色索引,因為這是24位真彩色,所以沒有調色板,自然也就沒有索引,所以是0。

       字節32-35:表示重要索引數,因為連索引都沒有,更沒有重要索引了,於是和上面一個部分的結果時一樣的,也是0。

       以上的39個字節被稱作位圖信息頭,用來表示和說明位圖的信息,注意,是位圖的信息而不是文件的信息,打個比方,正常情況下,你擁有兩個耳朵,一個鼻子, 兩只眼睛,一對眉毛,一個嘴,這都是你作為一個人的特征,你擁有的是黑色頭發,這是你作為你的特征。不同的位圖的位圖信息頭一般是不同的,不同的位圖,文 件信息頭是很有可能相同的。

       接下來的是位圖數據,因為這是24位真彩色,沒有調色板,所以接下來的一定是位圖數據,也就是通常說的RGB值,看到這些位圖數據的起始位置是36,和前面的偏移量是相同的,這里又有需要注意的地方了。

        1. 由於采用的是小端法,所以這里取出來的值實際上是B G R,取第一個三元組(5E FF5E),也就是這個像素的B GR 分別是5E FF 5E, 不過這正巧B 和 R的值是相同的。

        2.  我在1里面用的這個像素而不是第一個像素,是因為這並不是我們程序員意義上的第一個像素,程序員的坐標原點是在屏幕上物體的左上角,但是這個第一個像素表 示的是左下角的第一個像素,也就是(5E FF 5E)表示的是這個圖像的最左下角的那個像素值。如果你是一個喜歡思考的人,你一定要問為什么是這個樣子的呢,表面上看這個無傷大雅的原點位置其實混亂了 我們的思維方式。這個問題的答案可能會讓你有點失望,因為最初玩計算機的是數學家,他們制定了很多計算機標准,比如bmp,在數學家的思維方式中中點才是 習慣的坐標原點,於是圖像的右下角也就成了坐標原點。那么有沒有可能將左上角看做坐標原點呢?這里要說的數學家們的思維絕對不是蓋的,他們想出了一個巧妙 的辦法,如果高度是負值,那么第一個像素三元組表示的就是第一個像素,為什么請自己想想(最可恨的提示:想想坐標原點其實還是在圖像的左下角)。

       如此上面就可以組成一個24位的位圖,但是有沒有結束呢?沒有,這讓我想到一句話,Everything will be OK inthe end. If it's not OK , it's not the end.這里沒有OK,所以也還沒有到end。

       看似上面的說明已經能夠完成一幅24位bmp的所有部分,但是下面要說的這個知識是涉及到的學科又是計算機組成結構和操作系統(所以學以致用非常重要),這個名詞在你以后很多優化策略中都是有很大的作用的,請記住這個名詞:數據對齊。

首先,要說明的是,對於windows默認的掃描的最小單位是4字節,也就是說一次性讀4字節,那么如果數據對齊是滿足這個條件的話,對數據的獲取 速度等等都是大有好處的,這個參見操作系統里面磁盤,內存調度那一塊的內容,然后,自然windows的文件如果能夠盡量滿足這個要求對文件的讀取速度是 大大的提高,所以bmp也滿足了這個特性。那么,具體是怎么做的呢?

       在我們這個例子中不存在這個情況,因為,在我們的例子中,寬度是16,一行16個像素,一個像素由3個字節表示,那么一行就是48個字節,48對於4取模 是0,也就是說天然滿足數據對齊的要求。如果我們是15*15,那么一行的字節數就變成了45,這個就不滿足數據對齊的要求了,那么,我們就在字節的最后 補充0,離45最近而又比45大的4的倍數是48,所以我們在一行數據的末尾補充3個字節的0。

       如果你覺得上面的還是很抽象,那么我舉一個極端的例子,假設圖像的大小是1*2(24位),圖像數據區的組成為20,20,20,30,30,30,當 然,在實際的bmp中沒有逗號,可以看到第一行是2020 20 ,不是4的倍數,那么我們補充一個字節的0,將第一行擴充為20 20 20 00, 同理,第三行是30 30 30 00,重新組成的數據區為20 20 20 00 30 30 30 00,計算機取一行數據的時候正好取得的是4的倍數,在這個例子中是2020 20 00,那么喜歡想問題的又會想,那計算機怎么識別多少個0是添加上去的呢?很簡單,在前面的位圖信息頭中,我們有圖像的寬度。

       說了那么多,很自然的發現,如果加入了數據對齊,那么位圖數據區的大小就未必是 圖片寬 ×每像素字節數×圖片高 能表示的了,因為加入了填充數據。我們可以根據下面的這個公式進行計算一行的字節數:

         

       

        bpp表示每像素比特數,在24位bmp位圖中就是24。

        在程序中可以表示為: 

         int iLineByteCnt =(((m_iImageWidth * m_iBitsPerPixel) + 31) >> 5) << 2;

        這些個公式都是的推導並不困難,如果有興趣,你可以自己驗證一下。

        有一行的實際比特數,那么就能得到圖像的真 數據區大小(也就是去掉填充比特的):

         m_iImageDataSize = iLineByteCnt * m_iImageHeight;

         只要有填充比特,那么掃描一行之后得到的一定不是下一行的數據,跳過填充數據的數量如下所示:

         skip = 4 - ((m_iImageWidth * m_iBitsPerPixel)>>3) & 3;

          如上所示,就是一個24位真彩色圖像的構成方式,理論上看完這些,你就應該能自己組織數據構成一個bmp了。當然,也許只是理論上,實際上如果你還沒明白的話,我會在操作篇進行演示。

 

三、如果32位,怎么辦?

        上面說了24位真彩色數據,是沒有調色板的,那么如果是同樣沒有調色板的32位多了一個比特的什么呢?多的這一個被稱作alpha,這個值表示的是透明 度,那么在數據區就是一個四元組表示的一個像素,排布方式是B G R A,當然相應位圖信息頭中有些數據的內容需要變化。如果想加深對bmp構成的了解,那么重新組織一下32位的數據信息頭是個很好的做法。

四、調色板不只是目錄

       調色板可以理解為一種索引,但又不僅僅是索引的作用,如果采用調色板的圖像那么就可以進行壓縮,我們可以把調色板想象為一種數組,每個元素4字節大,下面,還是用一個具體的例子進行說明:

        我們創建一個16*16的16色位圖,數據少,好分析,用UE打開,顯示的數據如下圖所示:

      

        首先,直觀上,我們可以看到,數據量比24位位圖要少很多,我們來和上面一樣一個一個說明:

        字節0-1:和24位的相同

        字節2-5:除了數據不同,表示的意義完全相同,表示的是圖像文件的大小

        字節6-9:同樣是保留

        字節a-d: 一樣的數據偏移,數據區開始的地方

        字節0e-11:暫時認為一定為28

        字節12-15:表示圖像的寬度16

        字節16-19:表示圖像的高度16

        字節1A-1B:永遠為1,規定如此。

        字節1C-1F:和上面一樣的意義,只不過這里是一個像素由4位表示

        字節1E-21:同樣表示不壓縮

        字節22-25:圖像數據大小

        字節26-29:水平分辨率

        字節2A-2D:垂直分辨率

        字節2E-32:實際使用的顏色索引,注意,這里的0表示使用全部索引

        字節32-35:重要索引數

        我想關於含有調色板的信息頭最首先想到的一個問題就是最后兩個部分,字節2E-32和字節32-35,為什么需要這兩個值,是因為在早期的計算機中,顯卡 相對比較落后,不一定能保證顯示所有顏色,所以在調色板中的顏色數據應盡可能將圖像中主要的顏色按順序排列在前面,字節32-35就表示了這個數值,然后 2E-32在如果這幅圖像沒有用到所有調色板項的時候會很有用處。但是在絕大數情況下都會是0的。

        在有調色板的圖像中,接下來的就是調色板項了,這是16位的位圖,那么我們有15個調色板項,我們把它截取出來以便於特殊說明:

      

      

         如果在尋找哪些是調色板項的時候你還是一個一個的數的話,那么我建議你返回前面再看一遍,絕對比你數要節省時間。這是這個圖像的調色板,四個四個一組,我們發現正好16組。

        我們取出前三個調色板項,(00 00 00 00),(00 00 80 00),(00 80 00 00),一樣排布方式是B G R A 形式的,這三個值得索引依次為0 1 2,我們取得數據區“第一個”像素值,FF,記住我們用的是16位的圖像,用4個比特位表示一個像素,那么這個FF實際上表示的是兩個像素(實際上是索 引),這兩個索引都是F,F 也就是最后一個調色板項,我們可以從圖中得到是(FF FF FF 00),就是白色,依次類推,可以通過查詢調色板的方式查找到實際的像素值。

        只要是調色板的方式都是按照上面的形式組織的,只是調色板的大小和數據區表示像素的位數不一樣而已。

五、輔助的補充

        首先,我們要說明的是我們一直說的暫時認定是0x28的那個數值,也就是字節0e-11,這個字節是因為一些歷史原因表示bmp版本的,由於現在很難用到其他幾種系統的電腦,所以一般都是0x28,其意義如下:     

               28h - Windows 3.1x, 95, NT, …

         0Ch - OS/21.x

         F0h - OS/2 2.x

         然后,我們要說下字節1E-21表示的壓縮為問題,在bmp中這個數值可以有一些幾種表示:     

            0 - 不壓縮 (使用BI_RGB表示)

      1 - RLE 8-使用8位RLE壓縮方式(用BI_RLE8表示)

      2 - RLE 4-使用4位RLE壓縮方式(用BI_RLE4表示)

      3 - Bitfields-位域存放方式(用BI_BITFIELDS表示)

        不壓縮就不用說明了吧,先來說明一下1和2,RLE表示的是行程編碼,后綴 8 和4 表示的是4位的壓縮還是8位的壓縮,行程編碼是最簡單最常見的壓縮方法。

        我想先進行說明的是為什么要壓縮,如果你是一個有一定經驗的程序員,優化是一個初級的高等程序問題,比如說你在一個4bpp的圖像中一行其實只有兩種顏 色,前面100個像素全是白色,后面100個像素全是黑色,那么按照前面說的如果在不壓縮的情況下,需要50個字節存儲前面的白色,50個字節存儲后面的 黑色。但是這是沒有必要的,為了避免在出現這種情況,制定bmp的數學家們使用了無損壓縮里面最基本的行程編碼。基本思想是將一段數據編碼成為兩個部分 (也可以說是兩個字節),第一個字節存儲重復色彩的數量,第二個字節存儲重復色彩的值,比如前面的100個像素是白色,100個像素是黑色轉換為RLE- 4編碼就是(100 白色像素的索引 100 黑色像素的索引 0),最后加一個0表示行程編碼的結束,因為一個長度為0是沒有意義的。這樣100個字節就變成了5個字節。RLE-8和RLE-4類似,只是8bpp和 4bpp的差別。

        對於BitField不能說是一種壓縮方式,更是一種表示方法,在嵌入式系統中比較常見,如果有興趣可以自己查詢資料,在這里我們用不到,就不在敘述了。

        也許你看完上面的介紹之后處於躍躍欲試的狀態,當然也有可能處於一頭霧水和我不感興趣這兩種狀態,不論是那種狀態,動手試一試能夠獲得成就感往往會使你確定自己到底對某件事物感不感興趣。

        為了演示最基本的和編碼底層相關的原理,我們首選是C\C++,MFC中有現成的操縱bitmap的結構和類,但是這里我們從0開始先自己構造出這個類。








免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM