PNG文件格式具體解釋


 

PNG文件結構分析(上:了解PNG文件存儲格式)

 

前言

我們都知道,在進行J2ME的手機應用程序開發的時候,在圖片的使用上,我們能夠使用PNG格式的圖片(甚至於在有的手機上,我們僅僅能夠使用PNG格式的圖片),雖然使用圖片能夠為我們的應用程序添加不少亮點,然而,僅僅支持PNG格式的圖片卻又限制了我們進一步發揮的可能性(事實上,應該說是因為手機平台上的處理能力有限)。 在MIDP2中,或者某些廠商(如NOKIA)提供的API中,提供了drawPixels/getPixels的方法,這些方法進一步提高了開發人員處理圖片的靈活性,然而,在MIDP2還未全然普及的今天,我們須要在MIDP1 .0中實現這類方法還屬於異想天開,因此,為了實現更高級的應用,我們必須充分挖掘PNG的潛力。

PNG的文件結構

對於一個PNG文件來說,其文件頭總是由位固定的字節來描寫敘述的:

十進制數 137 80 78 71 13 10 26 10
十六進制數 89 50 4E 47 0D 0A 1A 0A

當中第一個字節0x89超出了ASCII字符的范圍,這是為了避免某些軟件將PNG文件當做文本文件來處理。文件里剩余的部分由3個以上的PNG的數據塊(Chunk)依照特定的順序組成,因此,一個標准的PNG文件結構應該例如以下:

PNG文件標志 PNG數據塊 …… PNG數據塊

PNG數據塊(Chunk)

PNG定義了兩種類型的數據塊,一種是稱為重要數據塊(critical chunk),這是標准的數據塊,還有一種叫做輔助數據塊(ancillary chunks),這是可選的數據塊。重要數據塊定義了4個標准數據塊,每一個PNG文件都必須包括它們,PNG讀寫軟件也都必需要支持這些數據塊。盡管PNG文件規范沒有要求PNG編譯碼器對可選數據塊進行編碼和譯碼,但規范提倡支持可選數據塊。

下表就是PNG中數據塊的類別,當中,重要數據塊部分我們使用深色背景加以區分。

PNG文件格式中的數據塊
數據塊符號
數據塊名稱
多數據塊
可選否
位置限制
IHDR 文件頭數據塊 第一塊
cHRM 基色和白色點數據塊 在PLTE和IDAT之前
gAMA 圖像γ數據塊 在PLTE和IDAT之前
sBIT 樣本有效位數據塊 在PLTE和IDAT之前
PLTE 調色板數據塊 在IDAT之前
bKGD 背景顏色數據塊 在PLTE之后IDAT之前
hIST 圖像直方圖數據塊 在PLTE之后IDAT之前
tRNS 圖像透明數據塊 在PLTE之后IDAT之前
oFFs (專用公共數據塊) 在IDAT之前
pHYs 物理像素尺寸數據塊 在IDAT之前
sCAL (專用公共數據塊) 在IDAT之前
IDAT 圖像數據塊 與其它IDAT連續
tIME 圖像最后改動時間數據塊 無限制
tEXt 文本信息數據塊 無限制
zTXt 壓縮文本數據塊 無限制
fRAc (專用公共數據塊) 無限制
gIFg (專用公共數據塊) 無限制
gIFt (專用公共數據塊) 無限制
gIFx (專用公共數據塊) 無限制
IEND 圖像結束數據 最后一個數據塊

為了簡單起見,我們如果在我們使用的PNG文件里,這4個數據塊按以上先后順序進行存儲,而且都僅僅出現一次。

數據塊結構

PNG文件里,每一個數據塊由4個部分組成,例如以下:

名稱 字節數 說明
Length (長度) 4字節 指定數據塊中數據域的長度,其長度不超過(231-1)字節
Chunk Type Code (數據塊類型碼) 4字節 數據塊類型碼由ASCII字母(A-Z和a-z)組成
Chunk Data (數據塊數據) 可變長度 存儲依照Chunk Type Code指定的數據
CRC (循環冗余檢測) 4字節 存儲用來檢測是否有錯誤的循環冗余碼

CRC(cyclic redundancy check)域中的值是對Chunk Type Code域和Chunk Data域中的數據進行計算得到的。CRC詳細算法定義在ISO 3309和ITU-T V.42中,其值按以下的CRC碼生成多項式進行計算:

x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1

以下,我們依次來了解一下各個重要數據塊的結構吧。

IHDR

文件頭數據塊IHDR(header chunk):它包括有PNG文件里存儲的圖像數據的基本信息,並要作為第一個數據塊出如今PNG數據流中,並且一個PNG數據流中僅僅能有一個文件頭數據塊。

文件頭數據塊由13字節組成,它的格式例如以下表所看到的。

域的名稱
字節數
說明
Width 4 bytes 圖像寬度,以像素為單位
Height 4 bytes 圖像高度,以像素為單位
Bit depth 1 byte 圖像深度:
索引彩色圖像:1,2,4或8
灰度圖像:1,2,4,8或16
真彩色圖像:8或16
ColorType 1 byte 顏色類型:
0:灰度圖像, 1,2,4,8或16
2:真彩色圖像,8或16
3:索引彩色圖像,1,2,4或8
4:帶α通道數據的灰度圖像,8或16
6:帶α通道數據的真彩色圖像,8或16
Compression method 1 byte 壓縮方法(LZ77派生算法)
Filter method 1 byte 濾波器方法
Interlace method 1 byte 隔行掃描方法:
0:非隔行掃描
1: Adam7(由Adam M. Costello開發的7遍隔行掃描方法)

因為我們研究的是手機上的PNG,因此,首先我們看看MIDP1.0對所使用PNG圖片的要求吧:

  • 在MIDP1.0中,我們僅僅能夠使用1.0版本號的PNG圖片。而且,所以的PNG重要數據塊都有特別要求:
    IHDR
  • 文件大小:MIDP支持隨意大小的PNG圖片,然而,實際上,假設一個圖片過大,會因為內存耗盡而無法讀取。
  • 顏色類型:全部顏色類型都有被支持,盡管這些顏色的顯示依賴於實際設備的顯示能力。同一時候,MIDP也能支持alpha通道,可是,全部的alpha通道信息都會被忽略而且當作不透明的顏色對待。
  • 色深:全部的色深都能被支持。
  • 壓縮方法:僅支持壓縮方式0(deflate壓縮方式),這和jar文件的壓縮方式全然同樣,所以,PNG圖片數據的解壓和jar文件的解壓能夠使用同樣的代碼。(事實上這也就是為什么J2ME能非常好的支持PNG圖像的原因:))
  • 濾波器方法:雖然在PNG的白皮書中僅定義了方法0,然而全部的5種方法都被支持!
  • 隔行掃描:盡管MIDP支持0、1兩種方式,然而,當使用隔行掃描時,MIDP卻不會真正的使用隔行掃描方式來顯示。
  • PLTE chunk:支持
  • IDAT chunk:圖像信息必須使用5種過濾方式中的方式0 (None, Sub, Up, Average, Paeth)
  • IEND chunk:當IEND數據塊被找到時,這個PNG圖像才覺得是合法的PNG圖像。
  • 可選數據塊:MIDP能夠支持下列輔助數據塊,然而,這卻不是必須的。

    bKGD cHRM gAMA hIST iCCP iTXt pHYs
    sBIT sPLT sRGB tEXt tIME tRNS zTXt

關於很多其它的信息,能夠參考http://www.w3.org/TR/REC-png.html

PLTE

調色板數據塊PLTE(palette chunk)包括有與索引彩色圖像(indexed-color image)相關的彩色變換數據,它僅與索引彩色圖像有關,並且要放在圖像數據塊(image data chunk)之前。

PLTE數據塊是定義圖像的調色板信息,PLTE能夠包括1~256個調色板信息,每個調色板信息由3個字節組成:

顏色

字節

意義

Red

1 byte

0 = 黑色, 255 = 紅

Green

1 byte

0 = 黑色, 255 = 綠色

Blue

1 byte

0 = 黑色, 255 = 藍色

因此,調色板的長度應該是3的倍數,否則,這將是一個非法的調色板。

對於索引圖像,調色板信息是必須的,調色板的顏色索引從0開始編號,然后是1、2……,調色板的顏色數不能超過色深中規定的顏色數(如圖像色深為4的時候,調色板中的顏色數不能夠超過2^4=16),否則,這將導致PNG圖像不合法。

真彩色圖像和帶α通道數據的真彩色圖像也能夠有調色板數據塊,目的是便於非真彩色顯示程序用它來量化圖像數據,從而顯示該圖像。

IDAT

圖像數據塊IDAT(image data chunk):它存儲實際的數據,在數據流中可包括多個連續順序的圖像數據塊。

IDAT存放着圖像真正的數據信息,因此,假設可以了解IDAT的結構,我們就行非常方便的生成PNG圖像。

IEND

圖像結束數據IEND(image trailer chunk):它用來標記PNG文件或者數據流已經結束,而且必需要放在文件的尾部。

假設我們細致觀察PNG文件,我們會發現,文件的結尾12個字符看起來總應該是這種:

00 00 00 00 49 45 4E 44 AE 42 60 82

不難明確,因為數據塊結構的定義,IEND數據塊的長度總是0(00 00 00 00,除非人為增加信息),數據標識總是IEND(49 45 4E 44),因此,CRC碼也總是AE 42 60 82。

實例研究PNG

下面是由Fireworks生成的一幅圖像,圖像大小為8*8,為了方便大家觀看,我們將圖像放大:



使用UltraEdit32打開該文件,例如以下:
00000000~00000007:

能夠看到,選中的頭8個字節即為PNG文件的標識。

接下來的地方就是IHDR數據塊了:

00000008~00000020:

  • 00 00 00 0D 說明IHDR頭塊長為13
  • 49 48 44 52 IHDR標識
  • 00 00 00 08 圖像的寬,8像素
  • 00 00 00 08 圖像的高,8像素
  • 04 色深,2^4=16,即這是一個16色的圖像(也有可能顏色數不超過16,當然,假設顏色數不超過8,用03表示更合適)
  • 03 顏色類型,索引圖像
  • 00 PNG Spec規定此處總為0(非0值為將來使用更好的壓縮方法預留),表示使壓縮方法(LZ77派生算法)
  • 00 同上
  • 00 非隔行掃描
  • 36 21 A3 B8 CRC校驗

00000021~0000002F:

可選數據塊sBIT,顏色採樣率,RGB都是256(2^8=256)

00000030~00000062:

這里是調色板信息

  • 00 00 00 27 說明調色板數據長為39字節,既13個顏色數
  • 50 4C 54 45 PLTE標識
  • FF FF 00 顏色0
  • FF ED 00 顏色1
  • …… ……
  • 09 00 B2 最后一個顏色,12
  • 5F F5 BB DD CRC校驗

00000063~000000C5:

這部分包括了pHYs、tExt兩種類型的數據塊共3塊,因為並不太重要,因此也不再具體描寫敘述了。

000000C0~000000F8:

以上選中部分是IDAT數據塊

  • 00 00 00 27 數據長為39字節
  • 49 44 41 54 IDAT標識
  • 78 9C…… 壓縮的數據,LZ77派生壓縮方法
  • DA 12 06 A5 CRC校驗

IDAT中壓縮數據部分在后面會有具體的介紹。

000000F9~00000104:

IEND數據塊,這部分正如上所說,通常都應該是

00 00 00 00 49 45 4E 44 AE 42 60 82

至此,我們已經能夠從一個PNG文件里識別出各個數據塊了。因為PNG中規定除重要數據塊外,其他的輔助數據塊都為可選部分,因此,有了這個標准后,我們能夠通過刪除全部的輔助數據塊來降低PNG文件的大小。(當然,須要注意的是,PNG格式能夠保存圖像中的層、文字等信息,一旦刪除了這些輔助數據塊后,圖像將失去原來的可編輯性。)

刪除了輔助數據塊后的PNG文件,如今文件大小為147字節,原文件大小為261字節,文件大小降低后,並不影響圖像的內容。

事實上,我們能夠通過改變調色板的色值來完畢一些又趣的事情,比方說實現雲彩/水波的流動效果,實現圖像的淡入淡出效果等等,在此,給出一個鏈接給大家看或許更直接:http://blog.csdn.net/flyingghost/archive/2005/01/13/251110.aspx,我寫此文也就是受此文的啟示的。

如上說過,IDAT數據塊是使用了LZ77壓縮算法生成的,因為受限於手機處理器的能力,因此,假設我們在生成IDAT數據塊時仍然使用LZ77壓縮算法,將會使效率大打折扣,因此,為了效率,僅僅能使用無壓縮的LZ77算法,關於LZ77算法的詳細實現,此文不打算深究,假設你對LZ77算法的JAVA實現有興趣,能夠參考下面兩個網站:

PNG文件結構分析(下:在手機上生成PNG文件)

(已閱讀 次)

上面我們已經對PNG的存儲格式有了了解,因此,生成PNG圖片僅僅須要依照以上的數據塊寫入文件就可以。

(因為IHDR、PLTE的結構都很easy,因此,這里我們僅僅是重點講一講IDAT的生成方法,IHDR和PLTE的數據內容都沿用以上的數據內容)

問題確實是這種,我們知道,對於大多數的圖形文件來說,我們都能夠將實際的圖像內容映射為一個二維的顏色數組,對於上面的PNG文件,因為它用的是16色的調色板(實際是13色),因此,對於圖片的映射能夠例如以下:

調色板對比圖
(調色板對比圖)

12 11 10 9 8 7 6 5
11 10 9 8 7 6 5 4
10 9 8 7 6 5 4 3
9 8 7 6 5 4 3 2
8 7 6 5 4 3 2 1
7 6 5 4 3 2 1 0
6 5 4 3 2 1 0 0
5 4 3 2 1 0 0 0

PNG Spec中指出,假設PNG文件不是採用隔行掃描方法存儲的話,那么,數據是依照行(ScanLine)來存儲的,為了區分第一行,PNG規定在每一行的前面加上0以示區分,因此,上面的圖像映射應該例如以下:

0 12 11 10 9 8 7 6 5
0 11 10 9 8 7 6 5 4
0 10 9 8 7 6 5 4 3
0 9 8 7 6 5 4 3 2
0 8 7 6 5 4 3 2 1
0 7 6 5 4 3 2 1 0
0 6 5 4 3 2 1 0 0
0 5 4 3 2 1 0 0 0

另外,須要注意的是,因為PNG在存儲圖像時為了節省空間,因此每一行是依照位(Bit)來存儲的,而並非我們想象的字節(Byte),假設你沒有忘記的話,我們的IHDR數據塊中的色深就指明了這一點,所以,為了湊成PNG所須要的IDAT,我們的數據得改成例如以下:

0 203 169 135 101
0 186 152 118 84
0 169 135 101 67
0 152 118 84 50
0 135 101 67 33
0 118 84 50 16
0 101 67 33 0
0 84 50 16 0

最后,我們對這些數據進行LZ77壓縮就能夠得到IDAT的正確內容了。

然而,事情並非這么簡單,由於我們研究的是手機上的PNG,假設須要在手機上完畢LZ77壓縮工作,消耗的時間是可想而知的,因此,我們得再想辦法加降低壓縮時消耗的時間。

好在LZ77也提供了無壓縮的壓縮方法(奇怪吧?),因此,我們僅僅須要簡單的使用無壓縮的方式寫入數據就能夠了,這樣盡管浪費了空間,卻換回了時間!

好了,讓我們看一看怎么樣湊成無壓縮的LZ77壓縮塊:

字節
意義
0~2 壓縮信息,固定為0x78, 0xda, 0x1
3~6 壓縮塊的LEN和NLEN信息
壓縮的數據
最后4字節 Adler32信息

當中的LEN是指數據的長度,占用兩個字節,對於我們的圖像來說,第一個Scan Line包括了5個字節(如第一行的0, 203, 169, 135, 101),所以LEN的值為5(字節/行) * 8(行) = 40(字節),生成字節為28 00(低字節在前),NLEN是LEN的補碼,即NLEN = LEN ^ 0xFFFF,所以NLEN的為 D7 FF,Adler32信息為24 A7 0B A4(詳細算法見源程序),因此,依照這種順序,我們生成IDAT數據塊,最后,我們將IHDR、PLTE、IDAT和IEND數據塊寫入文件里,就能夠得到PNG文件了,如圖:


(選中的部分為生成的“壓縮”數據)

至此,我們已經可以採用最快的時間將數組轉換為PNG圖片了。

 

參考資料:

PNG文件格式白皮書:http://www.w3.org/TR/REC-png.html
為數不多的中文PNG格式說明:http://dev.gameres.com/Program/Visual/Other/PNGFormat.htm
RFC-1950(ZLIB Compressed Data Format Specification):ftp://ds.internic.net/rfc/rfc1950.txt
RFC-1950(DEFLATE Compressed Data Format Specification):ftp://ds.internic.net/rfc/rfc1951.txt
LZ77算法的JAVA實現:http://jazzlib.sourceforge.net/
LZ77算法的JAVA實現,包含J2ME版本號:http://www.jcraft.com/jzlib/index.html


免責聲明!

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



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