PNG文件格局:
PNG文件格局分為PNG-24和PNG-8,其最大的差別是PNG-24是用24位來保留一個像素值,是真彩色,而PNG-8是用8位索引值來在調色盤中索引一個色彩,由於一個索引值的最大上限為2的8次方既128,故調色盤中色彩數最多為128種,所以該文件格局又被叫做PNG-8 128仿色。
PNG-24由於其圖片容量過大,而且在諾基亞和Moto等某些機型上創立圖片失敗和顯示不准確等異常時有產生,有時還會嚴重拖慢顯示速度,故並不常用,CoCoMo認為這些異常和平台底層的圖像解壓不無關系。不過該格局最大的長處是可以保留Alpha通道,同事也曾有過利用該圖片格局實現Alpha 混雜的先例,想來隨著技巧的發展,手機硬件平台的晉升,Alpha混雜必定會被廣泛的利用,到那時該格局的最大上風才會真正施展。
PNG-8文件是目前廣泛利用的PNG圖像格局,其重要有六大塊組成:
1.文件頭
2.IHDR塊
3.PLTE塊
4.tRNS塊
5.IDAT塊
6.文件尾
這六大塊按次序排列,也就是說IDAT塊永遠是在PLTE塊之後,期間也會有許多其他的區塊用來描寫信息,例如圖像的最後修正時間是多少,圖像的創立者是誰等,不過這些區塊的信息對我們來說都是可有可無的描寫信息,故壓縮時一般先向這些區塊開刀。
數據塊:
除了文件頭,其中四大數據塊和文件尾都是由同一的數據塊文件結構描寫的:
Chunk Length: 4byte
Chunk Type: 4byte
Chunk Data: Chunk Length的長度
Chunk CRC: 4byte
例如IHDR塊的數據長度為13,既
Chunk Length = 13
Chunk Type = "IHDR"
文件頭:
用來標示PNG文件,為固定的64個字節:0x89504e47 0x0d0a1a0a
IHDR塊:
用來描寫圖像的基礎信息,其格局為:
圖像寬: 4byte
圖像高: 4byte
圖像色深: 4byte
色彩類型: 1byte
壓縮方法: 1byte
濾波方法: 1byte
掃描方法: 1byte
曾經有人問過我,撒叫濾波方法和掃描方法,汗,說實話我也不知道,不過我們是在做手機游戲,不是在搞圖形學不是嘛。
PLTE塊:
這個就是傳說中放置調色盤數據的處所啦,其格局為:
循環
RED: 1byte
GREEN:1byte
BLUE: 1byte
END
循環長度嘛,不就是Chunk Length / 3的長度嘛,而且Chunk Length必定為3的倍數。
tRNS塊:
這個塊時有時無,重要是看你是否應用了透明色。該區塊的格局為:
循環
if(對應調色盤色彩非透明)
0xFF: 1byte
else
0x00: 1byte
END
循環長度為調色盤的色彩數,相當於調色盤色彩表的一個對應表,標識該色彩是否透明,0xFF不透明,0x00透明。故假如用UltraEdit查看PNG文件的二進制編碼,假如看到一***FF,一般就是tRNS區塊啦,由於一個PNG文件一般只有一個透明色。
IDAT塊:
這個就是存放圖像數據的處所啦,這裡要留心的是一個PNG文件可能有多個IDAT區塊,而其他三大區塊只可能有一個。
IDAT 區塊是經過壓縮的,所以數據不可讀,壓縮算法一般為LZ77滑動窗口算法,假如硬要看裡面的數據的話,用zlib庫也是可以的,CoCoMo當年就見過 Windows Mobile上的帝國時代巨***的用zlib庫壓縮和解壓該區塊來進一步減少PNG文件大小,真是寸K寸金啊。
IEND塊:
該區塊固然也按照數據塊的結構,但Chunk Data是沒有的,所以是固定的96個字節:0x00000000 0x49454e44 0xae426082
PNG圖像壓縮:
懂得了PNG的文件結構,壓縮就有的放矢了。壓縮有6個級別,可以根據需要選擇。
Level1:讀取PNG文件,將除六大塊之外的所有區塊都過濾掉
Level2:文件頭是固定的0x89504e47 0x0d0a1a0a,文件尾是固定的0x00000000 0x49454e44 0xae426082,往掉!
Level3:每個區塊的Chunk Type我們是否需要呢?很明顯,我們自己寫的壓縮格局自己應當明白是按照什麼樣的次序,往掉!
Level4:每個區塊的Chunk Length我們是否需要呢?
IHDR塊:定長13個字節,明顯不需要,往掉。
PLTE塊:最多128個色彩,為撒要用4byte來記錄區塊長度而不是用1byte來記錄色彩數呢?
tRNS塊:既然有色彩數,tRNS又是調色盤色彩表的對應表,既數目與色彩數雷同,為撒還需要呢?
IDAT塊:我想這個是唯一需要4byte來記錄長度的區塊。
Level5:每個區塊的Chunk CRC是否需要呢?
由於盤算CRC需要一些時間,但對於字節較少的區塊一般可以疏忽不計,所以對於這個標題還是由程序員自己決定吧。對於CRC的盤算可以參看CoCoMo的另一篇Blog“PNG文件的CRC碼盤算”
Level6:每個區塊我們是否要原封不動的保留期數據呢?
IHDR塊:除了寬、高、色深是需要的,後面那4byte的信息是固定的0x03000000
PLTE塊:為撒要用3byte來表現RGB而不是2byte的565格局?壓縮方法可以參看CoCoMo的另一篇Blog“關於PNG圖像壓縮的一點感悟”
tRNS塊:我想tRNS塊是冗余最多的區塊了吧,大段大段的0xFF明顯沒有必要,一般的PNG文件只有一個透明色,為撒要用對應表的方法而不是一個索引來記錄到底哪個是透明色呢?由於色彩數最多128,所以只需1byte就可以代替tRNS那麼多0xFF啦。
IDAT塊:麼想法,假如你夠***,把zlib加進來吧!
PNG圖像解壓:
創立了自定義的文件,J2ME端讀取後,就面臨解壓的標題了。我們可以利用此函數來創立Image:static Image
createImage(byte[] imageData, int imageOffset, int imageLength)
條件是傳進的imageData與PNG未被壓縮前的一致。由於PNG文件格局是固定的,所以讀取自定義的壓縮文件後,開端將那些默認的數據再添加進往,實現解壓的目標。下面就開端解壓之旅吧!
首先要創立一個ByteArrayOutputStream out,
1.寫進文件頭:
out.writeInt(0x89504e47);
out.writeInt(0x0d0a1a0a);
2.寫進IHDR塊
out.writeInt(13);
out.writeInt(0x49484452); //0x49484452為Chunk Type "IHDR"
out.writeInt(width);
out.writeInt(height);
out.writeByte(depth);
out.writeInt(0x03000000); //壓縮時捨掉的4byte,默認0x03000000
out.writeInt(crc);
其他區塊方法一致,故略過。。。
3.寫進文件尾
out.writeInt(0x00000000);
out.writeInt(0x49454e44);
out.writeInt(0xae426082);
4.轉換成數組,創立Image
byte[] pngBuffer = out.toByteArray();
Image image = Image.createImage(pngBuffer, 0, pngBuffer.length);
哈哈,大功告成。這裡留心假如中途數據寫進有錯誤,經常會呈現創立Image失敗的異常,而且非常不好調試,不過只要自定的壓縮格局定下來後,對應的創立Image的函數只要寫一次,以後基礎不會出標題哈。
PNG圖像加解密:
許多人都擔心自己辛苦創作的美麗的美術圖片很easy就被別人拿到了,究其原因是由於PNG文件格局是固定的,稍微懂得的人用UltraEdit很輕易就能找到IHDR,PLTE等標識了。CoCoMo就經常看GameLoft的圖像文件,哈哈。一般是2byte的Length,然後緊接著圖片數據,都放在一個文件裡,直接拷貝2進制然後粘貼到一個新文件裡就是一幅圖。後來的加密技巧會把PNG分塊,例如前100個字節一塊,緊接著1K一塊,最後剩余字節一塊,然後把塊次序打亂,用2byte來記錄總長度,1byte記錄次序,但是這並沒有從基本上打消IHDR,IEND這些顯眼的定位標識,似乎在對破解者說:嘿,看,我就在這裡!
現在懂得了之前的壓縮和解壓技巧,這個標題也就迎刃而解了,由於Chunk Length,Chunk Type和Chunk CRC這些東西都消散了,甚至連數據塊本身的數據都修正了,我可以按照ImageWidth、ImageHeight、ImageDepth的次序寫數據,也可以倒過來寫。我想再牛的PNG分析器也是無能為力的吧,唯一可以定位的就只有IDAT區塊了,不過就算得到該區塊的數據,也應當是一張黑白圖。
不過在加解密的范疇真是道高一尺,魔高一丈,CoCoMo很希看和各位共同探討,共同提高!