png圖片結構分析與加密解密原理
PNG檔案格式分為PNG-24和PNG-8,其最大的區別是PNG-24是用24位來儲存一個畫素值,是真彩色,而PNG-8是用8位索引值來在調色盤 中索引一個顏色,因為一個索引值的最大上限為2的8次方既128,故調色盤中顏色數最多為128種,所以該檔案格式又被叫做PNG-8 128仿色。PNG-24因為其圖片容量過大,而且在Nokia和Moto等某些機型上建立圖片失敗和顯示不正確等異常時有發生,有時還會嚴重拖慢顯示速度,故並不常 用,CoCoMo認為這些異常和平臺底層的影象解壓不無關係。不過該格式最大的優點是可以儲存Alpha通道,同事也曾有過利用該圖片格式實現Alpha 混合的先例,想來隨著技術的發展,手機硬體平臺的提升,Alpha混合一定會被廣泛的應用,到那時該格式的最大優勢才會真正發揮。
8 bit PNGs use an indexed color palette like GIF. If you want variable transparency, use 32bit PNGs (24 bit color, 8 bit alpha). If you don't care about transparency, use 24 bit PNGs.
PNG-8檔案是目前廣泛應用的PNG影象格式,其主要有六大塊組成:
1.PNG檔案標誌,為固定的64個位元組:0x89504e47 0x0d0a1a0a
2.檔案頭資料塊IHDR(header chunk)
3.調色盤資料塊PLTE(palette chunk)
4.sBIT,tRNS塊 等。。。
5.影象資料塊IDAT(image data chunk)
6.影象結束資料IEND(image trailer chunk),固定的96個位元組:0x00000000 0x49454e44 0xae426082
這六大塊按順序排列,也就是說IDAT塊永遠是在PLTE塊之後,期間也會有許多其他的區塊用來描述資訊,例如影象的最後修改時間是多少,影象的建立者是誰等,不過這些區塊的資訊對我們來說都是可有可無的描述資訊,故壓縮時一般先向這些區塊開刀。
資料塊1-4:
除了PNG檔案標誌,其中四大資料塊和檔案尾都是由統一的資料塊檔案結構描述的:
Chunk Length: 4byte
Chunk Type: 4byte
Chunk Data: Chunk Length的長度
Chunk CRC: 4byte
例如IHDR塊的資料長度為13,既
Chunk Length = 13
Chunk Type = "IHDR"
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
IEND資料塊的長度總是0(00 00 00 00,除非人為加入資訊),資料標識總是IEND(49 45 4E 44),因此,CRC碼也總是AE 42 60 82。
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區塊了,不過就算得到該區塊的資料,也應該是一張黑白圖。
-----------------------------------------------------------------
-----------------------------------------------------------------
-----------------------------------------------------------------
附錄
PNG檔案結構分析(上:瞭解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
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位元組,檔案大小減少後,並不影響影象的內容。
如 上說過,IDAT資料塊是使用了LZ77壓縮演算法生成的,由於受限於手機處理器的能力,因此,如果我們在生成IDAT資料塊時仍然使用LZ77壓縮演算法, 將會使效率大打折扣,因此,為了效率,只能使用無壓縮的LZ77演算法,關於LZ77演算法的具體實現,此文不打算深究,如果你對LZ77演算法的JAVA實現 有興趣,可以參考以下兩個站點:
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圖片了
參考資料: