PNG 是 J2ME 遊戲與應用的標準圖片格式。深入理解其區塊結構,可實現極致壓縮(減小 JAR 體積)、自定義解壓流程(保護美術資產)與加密防盜。本文系統拆解 PNG-8 格式,並提供六級壓縮策略、自定義解壓流程與加密思路。
PNG-8 使用 8-bit 索引色(最多 256 色),由六大必要區塊按序組成:
89 50 4E 47 0D 0A 1A 0A00 00 00 00 49 45 4E 44 AE 42 60 82每個區塊結構:Length(4B) + Type(4B) + Data(Length) + CRC(4B)。
| 等級 | 操作 | 說明 |
|---|---|---|
| L1 | 移除非必要區塊 | 保留上述六大區塊,刪除 tIME、tEXt 等描述性區塊 |
| L2 | 移除固定檔頭/檔尾 | 解壓時由程式固定寫入,省 20 bytes |
| L3 | 移除區塊類型碼 | 自定義格式順序固定,Type 可省略 |
| L4 | 精簡區塊長度 | IHDR 固定 13、PLTE 用 1 byte 記色數、tRNS 同色數、IDAT 需 4 bytes |
| L5 | 選擇性移除 CRC | 小區塊 CRC 計算成本低,視效能決定保留與否 |
| L6 | 壓縮區塊數據本身 | IHDR 尾 4 bytes 固定值、PLTE 改用 RGB565 (2 bytes/色)、tRNS 單透明色僅存索引(1 byte)、IDAT 可再接 zlib |
核心思路:讀取自定義二進位資源 → 依序重建標準 PNG 區塊 → 呼叫 Image.createImage(byte[], offset, length)。
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 1. 檔案頭
out.writeInt(0x89504E47); out.writeInt(0x0D0A1A0A);
// 2. IHDR
out.writeInt(13); out.writeInt(0x49484452); // "IHDR"
out.writeInt(width); out.writeInt(height);
out.writeByte(depth); out.writeInt(0x03000000); // 固定壓縮/濾波/掃描
out.writeInt(crc32(IHDR_data)); // 需自行實作 CRC32
// 3. PLTE (若有)
out.writeInt(palette.length * 3); out.writeInt(0x504C5445); // "PLTE"
for (int rgb : palette) {
out.writeByte(rgb >> 16); out.writeByte(rgb >> 8); out.writeByte(rgb);
}
out.writeInt(crc32(PLTE_data));
// 4. tRNS (若有透明色)
out.writeInt(palette.length); out.writeInt(0x74524E53); // "tRNS"
for (int i=0; i
注意: J2ME 無內建 CRC32,需自行實作或移植精簡版;任何位元組錯誤都會導致 createImage 拋出異常且難以除錯,建議先在 PC 端驗證生成的 PNG 是否可被標準檢視器開啟。
傳統做法:檔案層面切分、打亂順序,但 IHDR/IEND 等特徵碼仍可被識別。
進階方案: 結合上述壓縮,連區塊類型、長度、CRC 皆移除,甚至打亂 IHDR 內部欄位順序(如先寫高度再寫寬度),並將 PLTE 改為 RGB565、tRNS 僅存透明索引。此時二進位資料完全不符合 PNG 規範,任何 PNG 分析器皆無法識別,唯有持有對應解壓邏輯的客戶端才能還原。IDAT 雖仍可被提取,但缺調色盤僅能得灰階圖,大幅降低資產價值。
.bin 放入 JAR。.bin 檔。掌握 PNG 結構與自定義序列化,不僅能大幅縮減 JAR 體積,更能在美術資產保護上建立獨特壁壘。