首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

PNG 圖片換色技術:調色板修改與 CRC 重算實作

     編輯:J2ME

核心原理

針對 索引色(Indexed Color,PNG 色彩類型 3) 圖片,像素資料不直接儲存 RGB,而是儲存調色板索引。若美術在原圖將「需換色區域」統一填為純紅 #FF0000(索引值固定),程式只需:

  1. 解析 PNG 區塊,定位 PLTE(調色板)區塊。
  2. 將該索引對應的 RGB 值替換為目標顏色。
  3. 重新計算 PLTE 及後續 IDAT 的 CRC-32 校驗碼。
  4. 重組寫出新 PNG 檔案。

此法避免逐像素解碼/編碼,極快且不損畫質,適合遊戲中「同一圖騎、不同顏色單位」的大量變體產出。

完整工具類實作(J2SE 環境,可移植至 J2ME 配合 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;
    }
}

美術協作規範

  1. 原圖必須為 PNG-8(索引色),不可為真彩色(PNG-24/32)。
  2. 「可換色區域」統一使用 同一索引值(建議索引 0 或 255),並填入 #FF0000 純紅作為標記色。
  3. 非換色區域勿使用該索引,避免誤替換。

J2ME 端整合建議

  • 預先在建構階段(Ant/Polish)跑批次處理,產出各顏色變體 PNG,打包進 JAR,運行期直接 Image.createImage() 載入,零運算開銷。
  • 若必須運行期動態換色(如玩家自訂顏色),可將上述邏輯移植至 MIDP,改用 InputStream/OutputStream 操作 byte[],並用 Image.createImage(byte[], 0, len) 解碼顯示。
  • 注意:MIDP 無 CRC32 類,需自行實作或引用輕量 CRC32 實作(約 30 行程式碼)。

常見坑點

現象原因解法
輸出圖片無法顯示CRC 計算錯誤(未包含 Chunk Type)CRC 必須覆蓋 Type + Data,不可僅算 Data
顏色未改變原圖非索引色、或目標索引未使用用圖片檢視器確認色彩類型為 3,且目標索引確有像素使用
透明度異常忽略 tRNS 區塊(索引色透明資訊)若原圖有 tRNS,複製時必須一併保留
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved