針對 索引色(Indexed Color,PNG 色彩類型 3) 圖片,像素資料不直接儲存 RGB,而是儲存調色板索引。若美術在原圖將「需換色區域」統一填為純紅 #FF0000(索引值固定),程式只需:
PLTE(調色板)區塊。PLTE 及後續 IDAT 的 CRC-32 校驗碼。此法避免逐像素解碼/編碼,極快且不損畫質,適合遊戲中「同一圖騎、不同顏色單位」的大量變體產出。
javax.microedition.io)import java.io.*;
import java.util.zip.CRC32;
import javax.microedition.lcdui.Image;
public class PngPaletteSwapper {
/** 將 indexed PNG 中指定調色板索引替換為新顏色並輸出新 byte[] */
public static byte[] swapColor(byte[] pngBytes, int paletteIndex, int newRGB) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(pngBytes);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 1. 驗證 PNG 簽名
byte[] sig = new byte[8];
in.read(sig);
if (!checkSignature(sig)) throw new IOException("Not a valid PNG");
out.write(sig);
// 2. 逐區塊處理
while (true) {
int len = readInt(in);
byte[] type = new byte[4];
in.read(type);
String typeStr = new String(type);
byte[] data = new byte[len];
in.read(data);
int crcOrig = readInt(in);
if (typeStr.equals("PLTE")) {
data = modifyPalette(data, paletteIndex, newRGB);
}
// 重算 CRC
CRC32 crc = new CRC32();
crc.update(type);
crc.update(data);
int crcNew = (int) crc.getValue();
// 寫出區塊
writeInt(out, len);
out.write(type);
out.write(data);
writeInt(out, crcNew);
if (typeStr.equals("IEND")) break;
}
return out.toByteArray();
}
// --- 輔助方法 ---
private static boolean checkSignature(byte[] s) {
return s[0]==(byte)0x89 && s[1]=='P' && s[2]=='N' && s[3]=='G'
&& s[4]==0x0D && s[5]==0x0A && s[6]==0x1A && s[7]==0x0A;
}
private static int readInt(InputStream is) throws IOException {
int b1=is.read(), b2=is.read(), b3=is.read(), b4=is.read();
if (b4==-1) throw new EOFException();
return (b1<<24)|(b2<<16)|(b3<<8)|b4;
}
private static void writeInt(OutputStream os, int v) throws IOException {
os.write(v>>24); os.write(v>>16); os.write(v>>8); os.write(v);
}
private static byte[] modifyPalette(byte[] plte, int idx, int rgb) {
// PLTE 長度必為 3 的倍數,每 3 byte = 1 RGB
int pos = idx * 3;
if (pos + 2 >= plte.length) return plte; // 索引越界直接回傳原資料
plte[pos] = (byte)(rgb >> 16);
plte[pos+1] = (byte)(rgb >> 8);
plte[pos+2] = (byte)rgb;
return plte;
}
}
#FF0000 純紅作為標記色。Image.createImage() 載入,零運算開銷。InputStream/OutputStream 操作 byte[],並用 Image.createImage(byte[], 0, len) 解碼顯示。CRC32 類,需自行實作或引用輕量 CRC32 實作(約 30 行程式碼)。| 現象 | 原因 | 解法 |
|---|---|---|
| 輸出圖片無法顯示 | CRC 計算錯誤(未包含 Chunk Type) | CRC 必須覆蓋 Type + Data,不可僅算 Data |
| 顏色未改變 | 原圖非索引色、或目標索引未使用 | 用圖片檢視器確認色彩類型為 3,且目標索引確有像素使用 |
| 透明度異常 | 忽略 tRNS 區塊(索引色透明資訊) | 若原圖有 tRNS,複製時必須一併保留 |