標籤:
前言
我們都知道,在進行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實現有興趣,可以參考以下兩個網站:
- http://jazzlib.sourceforge.net/
- http://www.jcraft.com/jzlib/index.html
PNG檔案結構分析(下:在手機上產生PNG檔案)
(已閱讀 次)
上面我們已經對PNG的儲存格式有了瞭解,因此,產生PNG圖片只需要按照以上的資料區塊寫入檔案即可。
(由於IHDR、PLTE的結構都非常簡單,因此,這裡我們只是重點講一講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
【轉】png檔案格式