J2ME平臺(tái)PNG圖像壓縮、解壓與加密技術(shù) 收藏新一篇: java圖書(shū)資源下載
在J2ME平臺(tái)上PNG圖片格式幾乎成為了標(biāo)準(zhǔn),無(wú)數(shù)臺(tái)手持設(shè)備上運(yùn)行的J2ME程序幾乎都選用PNG來(lái)顯示圖像,包括大量的手機(jī)游戲以及手機(jī)應(yīng)用,所以對(duì)PNG文件格式的了解,可以更有效的減少Jar Size,保護(hù)自有知識(shí)產(chǎn)權(quán)。
CoCoMo曾經(jīng)對(duì)PNG文件進(jìn)行過(guò)一段時(shí)間的研究,包括圖像壓縮、解壓以及加解密等,現(xiàn)將研究心得記錄如下:
PNG文件格式:
PNG文件格式分為PNG-24和PNG-8,其最大的區(qū)別是PNG-24是用24位來(lái)保存一個(gè)像素值,是真彩色,而PNG-8是用8位索引值來(lái)在調(diào)色盤(pán)中索引一個(gè)顏色,因?yàn)橐粋€(gè)索引值的最大上限為2的8次方既128,故調(diào)色盤(pán)中顏色數(shù)最多為128種,所以該文件格式又被叫做PNG-8 128仿色。
PNG-24因?yàn)槠鋱D片容量過(guò)大,而且在Nokia和Moto等某些機(jī)型上創(chuàng)建圖片失敗和顯示不正確等異常時(shí)有發(fā)生,有時(shí)還會(huì)嚴(yán)重拖慢顯示速度,故并不常用,CoCoMo認(rèn)為這些異常和平臺(tái)底層的圖像解壓不無(wú)關(guān)系。不過(guò)該格式最大的優(yōu)點(diǎn)是可以保存Alpha通道,同事也曾有過(guò)利用該圖片格式實(shí)現(xiàn)Alpha混合的先例,想來(lái)隨著技術(shù)的發(fā)展,手機(jī)硬件平臺(tái)的提升,Alpha混合一定會(huì)被廣泛的應(yīng)用,到那時(shí)該格式的最大優(yōu)勢(shì)才會(huì)真正發(fā)揮。
PNG-8文件是目前廣泛應(yīng)用的PNG圖像格式,其主要有六大塊組成:
1.文件頭
2.IHDR塊
3.PLTE塊
4.tRNS塊
5.IDAT塊
6.文件尾
這六大塊按順序排列,也就是說(shuō)IDAT塊永遠(yuǎn)是在PLTE塊之后,期間也會(huì)有許多其他的區(qū)塊用來(lái)描述信息,例如圖像的最后修改時(shí)間是多少,圖像的創(chuàng)建者是誰(shuí)等,不過(guò)這些區(qū)塊的信息對(duì)我們來(lái)說(shuō)都是可有可無(wú)的描述信息,故壓縮時(shí)一般先向這些區(qū)塊開(kāi)刀。
數(shù)據(jù)塊:
除了文件頭,其中四大數(shù)據(jù)塊和文件尾都是由統(tǒng)一的數(shù)據(jù)塊文件結(jié)構(gòu)描述的:
Chunk Length: 4byte
Chunk Type: 4byte
Chunk Data: Chunk Length的長(zhǎng)度
Chunk CRC: 4byte
例如IHDR塊的數(shù)據(jù)長(zhǎng)度為13,既
Chunk Length = 13
Chunk Type = "IHDR"
文件頭:
用來(lái)標(biāo)示PNG文件,為固定的64個(gè)字節(jié):0x89504e47 0x0d0a1a0a
IHDR塊:
用來(lái)描述圖像的基本信息,其格式為:
圖像寬: 4byte
圖像高: 4byte
圖像色深: 4byte
顏色類(lèi)型: 1byte
壓縮方法: 1byte
濾波方法: 1byte
掃描方法: 1byte
曾經(jīng)有人問(wèn)過(guò)我,撒叫濾波方法和掃描方法,汗,說(shuō)實(shí)話(huà)我也不知道,不過(guò)我們是在做手機(jī)游戲,不是在搞圖形學(xué)不是嘛。
PLTE塊:
這個(gè)就是傳說(shuō)中放置調(diào)色盤(pán)數(shù)據(jù)的地方啦,其格式為:
循環(huán)
RED: 1byte
GREEN:1byte
BLUE: 1byte
END
循環(huán)長(zhǎng)度嘛,不就是Chunk Length / 3的長(zhǎng)度嘛,而且Chunk Length一定為3的倍數(shù)。
tRNS塊:
這個(gè)塊時(shí)有時(shí)無(wú),主要是看你是否使用了透明色。該區(qū)塊的格式為:
循環(huán)
if(對(duì)應(yīng)調(diào)色盤(pán)顏色非透明)
0xFF: 1byte
else
0x00: 1byte
END
循環(huán)長(zhǎng)度為調(diào)色盤(pán)的顏色數(shù),相當(dāng)于調(diào)色盤(pán)顏色表的一個(gè)對(duì)應(yīng)表,標(biāo)識(shí)該顏色是否透明,0xFF不透明,0x00透明。故如果用UltraEdit查看PNG文件的二進(jìn)制編碼,如果看到一大片F(xiàn)F,一般就是tRNS區(qū)塊啦,因?yàn)橐粋€(gè)PNG文件一般只有一個(gè)透明色。
IDAT塊:
這個(gè)就是存放圖像數(shù)據(jù)的地方啦,這里要注意的是一個(gè)PNG文件可能有多個(gè)IDAT區(qū)塊,而其他三大區(qū)塊只可能有一個(gè)。
IDAT區(qū)塊是經(jīng)過(guò)壓縮的,所以數(shù)據(jù)不可讀,壓縮算法一般為L(zhǎng)Z77滑動(dòng)窗口算法,如果硬要看里面的數(shù)據(jù)的話(huà),用zlib庫(kù)也是可以的,CoCoMo當(dāng)年就見(jiàn)過(guò)Windows Mobile上的帝國(guó)時(shí)代巨變態(tài)的用zlib庫(kù)壓縮和解壓該區(qū)塊來(lái)進(jìn)一步減少PNG文件大小,真是寸K寸金啊。
IEND塊:
該區(qū)塊雖然也按照數(shù)據(jù)塊的結(jié)構(gòu),但Chunk Data是沒(méi)有的,所以是固定的96個(gè)字節(jié):0x00000000 0x49454e44 0xae426082
PNG圖像壓縮:
了解了PNG的文件結(jié)構(gòu),壓縮就有的放矢了。壓縮有6個(gè)級(jí)別,可以根據(jù)需要選擇。
Level1:讀取PNG文件,將除六大塊之外的所有區(qū)塊都過(guò)濾掉
Level2:文件頭是固定的0x89504e47 0x0d0a1a0a,文件尾是固定的0x00000000 0x49454e44 0xae426082,去掉!
Level3:每個(gè)區(qū)塊的Chunk Type我們是否需要呢?很明顯,我們自己寫(xiě)的壓縮格式自己應(yīng)該清楚是按照什么樣的順序,去掉!
Level4:每個(gè)區(qū)塊的Chunk Length我們是否需要呢?
IHDR塊:定長(zhǎng)13個(gè)字節(jié),明顯不需要,去掉。
PLTE塊:最多128個(gè)顏色,為撒要用4byte來(lái)記錄區(qū)塊長(zhǎng)度而不是用1byte來(lái)記錄顏色數(shù)呢?
tRNS塊:既然有顏色數(shù),tRNS又是調(diào)色盤(pán)顏色表的對(duì)應(yīng)表,既數(shù)量與顏色數(shù)相同,為撒還需要呢?
IDAT塊:我想這個(gè)是唯一需要4byte來(lái)記錄長(zhǎng)度的區(qū)塊。
Level5:每個(gè)區(qū)塊的Chunk CRC是否需要呢?
因?yàn)橛?jì)算CRC需要一些時(shí)間,但對(duì)于字節(jié)較少的區(qū)塊一般可以忽略不計(jì),所以對(duì)于這個(gè)問(wèn)題還是由程序員自己決定吧。對(duì)于CRC的計(jì)算可以參看CoCoMo的另一篇Blog“PNG文件的CRC碼計(jì)算”
Level6:每個(gè)區(qū)塊我們是否要原封不動(dòng)的保存期數(shù)據(jù)呢?
IHDR塊:除了寬、高、色深是需要的,后面那4byte的信息是固定的0x03000000
PLTE塊:為撒要用3byte來(lái)表示RGB而不是2byte的565格式?壓縮方法可以參看CoCoMo的另一篇Blog“關(guān)于PNG圖像壓縮的一點(diǎn)感悟”
tRNS塊:我想tRNS塊是冗余最多的區(qū)塊了吧,大段大段的0xFF明顯沒(méi)有必要,一般的PNG文件只有一個(gè)透明色,為撒要用對(duì)應(yīng)表的方法而不是一個(gè)索引來(lái)記錄到底哪個(gè)是透明色呢?由于顏色數(shù)最多128,所以只需1byte就可以代替tRNS那么多0xFF啦。
IDAT塊:么想法,如果你夠變態(tài),把zlib加進(jìn)來(lái)吧!
PNG圖像解壓:
創(chuàng)建了自定義的文件,J2ME端讀取后,就面臨解壓的問(wèn)題了。我們可以利用此函數(shù)來(lái)創(chuàng)建Image:
static Image
createImage(byte[] imageData, int imageOffset, int imageLength)
前提是傳入的imageData與PNG未被壓縮前的一致。因?yàn)镻NG文件格式是固定的,所以讀取自定義的壓縮文件后,開(kāi)始將那些默認(rèn)的數(shù)據(jù)再添加進(jìn)去,實(shí)現(xiàn)解壓的目的。下面就開(kāi)始解壓之旅吧!
首先要?jiǎng)?chuàng)建一個(gè)ByteArrayOutputStream out,
1.寫(xiě)入文件頭:
out.writeInt(0x89504e47);
out.writeInt(0x0d0a1a0a);
2.寫(xiě)入IHDR塊
out.writeInt(13);
out.writeInt(0x49484452); //0x49484452為Chunk Type "IHDR"
out.writeInt(width);
out.writeInt(height);
out.writeByte(depth);
out.writeInt(0x03000000); //壓縮時(shí)舍掉的4byte,默認(rèn)0x03000000
out.writeInt(crc);
其他區(qū)塊方法一致,故略過(guò)。。。
3.寫(xiě)入文件尾
out.writeInt(0x00000000);
out.writeInt(0x49454e44);
out.writeInt(0xae426082);
4.轉(zhuǎn)換成數(shù)組,創(chuàng)建Image
byte[] pngBuffer = out.toByteArray();
Image image = Image.createImage(pngBuffer, 0, pngBuffer.length);
哈哈,大功告成。這里注意如果中途數(shù)據(jù)寫(xiě)入有錯(cuò)誤,經(jīng)常會(huì)出現(xiàn)創(chuàng)建Image失敗的異常,而且非常不好調(diào)試,不過(guò)只要自定的壓縮格式定下來(lái)后,對(duì)應(yīng)的創(chuàng)建Image的函數(shù)只要寫(xiě)一次,以后基本不會(huì)出問(wèn)題哈。
PNG圖像加解密:
很多人都擔(dān)心自己辛苦創(chuàng)作的漂亮的美術(shù)圖片很easy就被別人拿到了,究其原因是由于PNG文件格式是固定的,稍微了解的人用UltraEdit很容易就能找到IHDR,PLTE等標(biāo)識(shí)了。CoCoMo就經(jīng)常看GameLoft的圖像文件,哈哈。一般是2byte的Length,然后緊接著圖片數(shù)據(jù),都放在一個(gè)文件里,直接拷貝2進(jìn)制然后粘貼到一個(gè)新文件里就是一幅圖。后來(lái)的加密技術(shù)會(huì)把PNG分塊,例如前100個(gè)字節(jié)一塊,緊接著1K一塊,最后剩余字節(jié)一塊,然后把塊順序打亂,用2byte來(lái)記錄總長(zhǎng)度,1byte記錄順序,但是這并沒(méi)有從根本上消除IHDR,IEND這些顯眼的定位標(biāo)識(shí),好像在對(duì)破解者說(shuō):嘿,看,我就在這里!
現(xiàn)在了解了之前的壓縮和解壓技術(shù),這個(gè)問(wèn)題也就迎刃而解了,因?yàn)镃hunk Length,Chunk Type和Chunk CRC這些東西都消失了,甚至連數(shù)據(jù)塊本身的數(shù)據(jù)都修改了,我可以按照ImageWidth、ImageHeight、ImageDepth的順序?qū)憯?shù)據(jù),也可以倒過(guò)來(lái)寫(xiě)。我想再牛的PNG分析器也是無(wú)能為力的吧,唯一可以定位的就只有IDAT區(qū)塊了,不過(guò)就算得到該區(qū)塊的數(shù)據(jù),也應(yīng)該是一張黑白圖。
不過(guò)在加解密的領(lǐng)域真是道高一尺,魔高一丈,CoCoMo很希望和各位共同探討,共同提高!