在 J2ME 開發早期,受限於 JAR 包大小與手機儲存空間,開發者常面臨動畫資源佔用過大的難題。傳統做法需將每一幀另存為 PNG 序列圖,不僅增加美術成本,更嚴重膨脹安裝包體積。本文介紹一個輕量級的 GIFDecode 類別,能直接解析 GIF 文件並逐幀播放,以單一資源檔替代多張圖片,大幅降低資源佔用。
GIFDecode 完整實現了 GIF89a 規範中的關鍵特性:
類別採用「幀物件池」設計:解碼完成後將每幀轉為 Image 物件並暫存於 Vector,執行期僅需按索引取用,避免重複解碼開銷。
private GIFDecode gifDecoder;
private int currentFrame = 0;
private int totalFrames;
private Image frameImage;
void initGIF() {
gifDecoder = new GIFDecode();
// 從 JAR 內部資源載入
gifDecoder.read(getClass().getResourceAsStream("/animation.gif"));
totalFrames = gifDecoder.getFrameCount();
}
void drawAnimation(Graphics g, int x, int y, int anchor) {
frameImage = gifDecoder.getFrame(currentFrame);
currentFrame = (currentFrame + 1) % totalFrames; // 循環播放
g.drawImage(frameImage, x, y, anchor);
}
protected void readHeader() {
String id = "";
for (int i = 0; i < 6; i++) id += (char) read();
if (!id.startsWith("GIF")) { status = STATUS_FORMAT_ERROR; return; }
readLSD(); // 讀取螢幕尺寸、全域色表標誌等
if (gctFlag && !err()) {
gct = readColorTable(gctSize);
bgColor = gct[bgIndex];
}
}
protected void readGraphicControlExt() {
read(); // 區塊大小
int packed = read();
dispose = (packed & 0x1c) >> 2; // 棄置方式
if (dispose == 0) dispose = 1; // 預設保留上一幀
transparency = (packed & 1) != 0; // 透明旗標
delay = readShort() * 10; // 延遲(毫秒)
transIndex = read(); // 透明色索引
read(); // 區塊結束符
}
演算法採用動態字典表,MaxStackSize = 4096 符合 GIF 規範上限。解碼過程中以位元流方式讀取編碼,遇到 CLEAR 碼重置字典,END_OF_INFORMATION 結束解碼。像素索引寫入 pixels[] 陣列,後續由 setPixels() 映射為 ARGB 色值。
protected void setPixels() {
int[] dest = new int[width * height];
// 根據上一幀棄置方式處理背景
if (lastDispose > 0) { ... }
// 處理交織掃描行序
int pass = 1, inc = 8, iline = 0;
for (int i = 0; i < ih; i++) {
int line = interlace ? computeInterlaceLine(iline++, pass, inc, ih) : i;
line += iy;
if (line < height) copyPixelLine(dest, line, i);
}
image = Image.createRGBImage(dest, width, height, false);
}
width × height × 4 bytes × 幀數。建議單一 GIF 幀數 ≤ 20、解析度 ≤ 176×208。read() 會完整解碼全檔,建議在載入畫面或背景執行緒執行。| 問題 | 原因 | 對策 |
|---|---|---|
| 動畫播放速度過快/過慢 | 未正確讀取 delay 欄位 | 確認 readGraphicControlExt() 中 delay = readShort() * 10(單位:百分之一秒→毫秒) |
| 透明背景變黑底 | 未將透明索引色值設為 0 | 在 readImage() 中 act[transIndex] = 0 後再 setPixels() |
| 部分手機顯示花屏 | 色表索引超出邊界或 LZW 解碼錯誤 | 加強 err() 檢查,確保 pixels[] 長度 = iw × ih |
drawRegion 的裝置,將幀圖合併為精靈圖集減少 Image 物件數。GIFDecode 以約 1.5 KB 類別大小,換取動畫資源 60% 以上的空間節省,是早期 J2ME 遊戲與應用不可或缺的利器。雖然現代平台已普及更高效的 APNG/WebP,但在存量裝置維護與嵌入式 Java 專案中,該方案依然具備實戰價值。