1. 程式人生 > >png圖片結構分析與加密解密原理

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圖片了

參考資料: