前言
GIF (Graphics Interchange Format) 是支援動畫的無損壓縮圖片格式,廣泛用於簡短動畫、載入指示器、UI 微互動。Java 標準庫 ImageIO 僅支援靜態 GIF 讀寫,生成動畫需使用第三方編碼器。本文以經典 AnimatedGifEncoder 為例,完整解析動畫 GIF 生成核心 API、幀控制、優化技巧與現代替代方案。
一、核心依賴與環境
Maven 依賴
com.github.jai-imageio
jai-imageio-core
1.4.0
com.madgag.gif
gifencoder
0.1.0
AnimatedGifEncoder 來源
原始碼常見於:
- JDK 範例代碼
javax.imageio.plugins.gif 內部類
- Acme Labs
GifEncoder.java
- Neuron 專案
AnimatedGifEncoder.java (最常用版本)
二、基本用法完整範例
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class GifAnimatorDemo {
public static void main(String[] args) {
try {
// 1. 讀取素材圖片 (建議相同尺寸)
BufferedImage frame1 = ImageIO.read(new File("src/frame1.png"));
BufferedImage frame2 = ImageIO.read(new File("src/frame2.png"));
BufferedImage frame3 = ImageIO.read(new File("src/frame3.png"));
// 2. 建立編碼器
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
// 3. 設定輸出檔案
encoder.start("output/animation.gif");
// 4. 設定重複次數:0 = 無限循環,>0 = 循環次數
encoder.setRepeat(0);
// 5. 新增幀:設定延遲 → 加入圖片
encoder.setDelay(500); // 500ms = 0.5秒
encoder.addFrame(frame1);
encoder.setDelay(300); // 300ms
encoder.addFrame(frame2);
encoder.setDelay(200); // 200ms
encoder.addFrame(frame3);
// 6. 完成編碼 (寫入檔案尾部、關閉資源)
encoder.finish();
System.out.println("GIF 動畫生成完成: output/animation.gif");
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、核心 API 詳解
| 方法 | 說明 | 備註 |
start(String file) | 指定輸出檔案路徑,開啟檔案流 | 必須最先呼叫 |
start(OutputStream os) | 輸出到串流 (如 HTTP 回應、ByteArrayOutputStream) | Web 應用常用 |
setRepeat(int iter) | 設定循環次數 | 0=無限,1=播放1次(顯示2次),預設0 |
setDelay(int ms) | 設定下一幀的延遲時間 (毫秒) | 每幀可不同,單位 10ms (GIF 規格) |
addFrame(BufferedImage img) | 加入當前幀 | 會依前一次 setDelay 設定延遲 |
addFrame(Image img, int delay) | 一次設定延遲並加入幀 | 便利方法 |
setFrameRate(float fps) | 設定幀率 (自動換算延遲) | setDelay(1000/fps) |
setSize(int w, int h) | 設定輸出尺寸 (自動縮放幀) | 若幀尺寸不一致建議設定 |
setTransparent(int rgb) | 設定透明色 (RGB 值) | 如 0x00FF00 綠色透明 |
setQuality(int q) | 設定量化品質 (1-30,越小越好) | 預設 10,影響檔案大小/品質 |
setDither(boolean) | 啟用/關閉抖動 | 漸層圖片建議開啟 |
finish() | 完成編碼,寫入結束區塊,關閉流 | 必須呼叫,否則檔案損壞 |
四、進階技巧與最佳實務
1. 記憶體優化:串流輸出避免佔用堆記憶體
public byte[] generateGifToBytes(List frames, int delayMs) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
encoder.start(baos);
encoder.setRepeat(0);
for (BufferedImage frame : frames) {
encoder.setDelay(delayMs);
encoder.addFrame(frame);
}
encoder.finish();
return baos.toByteArray();
}
2. 尺寸標準化:自動縮放異尺寸幀
encoder.setSize(480, 270); // 統一輸出 16:9
for (BufferedImage frame : frames) {
encoder.addFrame(frame); // 內部自動縮放
}
3. 透明背景處理
// 將白色背景設為透明
encoder.setTransparent(0xFFFFFF); // 白色 RGB
// 或偵測首像素顏色作為透明色
int bgColor = frame.getRGB(0, 0);
encoder.setTransparent(bgColor);
4. 高品質量化設定
// 品質優先 (檔案較大)
encoder.setQuality(1); // 最佳品質
encoder.setDither(true); // 啟用 Floyd-Steinberg 抖動
// 檔案大小優先
encoder.setQuality(20); // 較低品質
encoder.setDither(false);
5. 從視頻/影片擷取幀生成 GIF (整合 FFmpeg)
// 概念流程:FFmpeg 擷取幀 → Java 讀取 → 編碼 GIF
// FFmpeg 指令:ffmpeg -i input.mp4 -vf "fps=10,scale=480:-1" frame_%04d.png
// Java 批次讀取:
File dir = new File("frames/");
File[] frameFiles = dir.listFiles((d, n) -> n.endsWith(".png"));
Arrays.sort(frameFiles); // 確保順序
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
encoder.start("video.gif");
encoder.setRepeat(0);
encoder.setDelay(100); // 10fps
for (File f : frameFiles) {
BufferedImage img = ImageIO.read(f);
encoder.addFrame(img);
}
encoder.finish();
五、效能與限制分析
| 指標 | 表現 | 建議 |
| 編碼速度 | 中等 (單執行緒、CPU 密集) | 大量幀建議異步處理、縮小尺寸 |
| 記憶體佔用 | 取決於幀尺寸 × 數量 | 串流輸出、分批處理、及時釋放 BufferedImage |
| 輸出檔案大小 | 較大 (無捨棄壓縮、256 色限制) | 降低幀率、縮小尺寸、減少幀數、啟用抖動 |
| 色彩表現 | 每幀 256 色 (全域/局部調色盤) | 照片級漸層效果差,適合插圖/簡單動畫 |
| 透明度支援 | 單一透明色 (Boolean 透明) | 不支援半透明 (Alpha channel) |
六、現代替代方案比較
| 方案 | 優點 | 缺點 | 適用場景 |
| AnimatedGifEncoder | 純 Java、無原生依賴、API 簡單 | 效能一般、功能固定、維護狀態不一 | 簡單動畫生成、教學、輕量應用 |
| gifencoder (madgag) | 現代 API、Kotlin 友善、效能較好 | 較新、社群較小 | 新專案、Kotlin 專案 |
| ImageIO + GIF ImageWriter (JDK 內建) | 零依賴、標準 API | 需手動寫入圖形控制擴展區塊、API 極繁瑣 | 不想引入第三方庫 |
| FFmpeg (外部程序) | 最強效能、支援所有格式、濾鏡豐富 | 需安裝原生二進制、部署複雜 | 視頻轉 GIF、高效能批次處理 |
| WebP / APNG | 支援 24-bit 色彩 + Alpha 透明、壓縮率高 | 瀏覽器相容性略低 (現代瀏覽器已全支援) | 現代 Web 應用首選替代格式 |
七、Web 應用整合範例 (Spring Boot)
Controller 端點
@RestController
@RequestMapping("/api/gif")
public class GifController {
@PostMapping("/generate")
public ResponseEntity generateGif(
@RequestParam("frames") List frames,
@RequestParam(defaultValue = "100") int delayMs,
@RequestParam(defaultValue = "0") int repeat) throws IOException {
List images = frames.stream()
.map(f -> {
try { return ImageIO.read(f.getInputStream()); }
catch (IOException e) { throw new UncheckedIOException(e); }
})
.toList();
byte[] gifBytes = GifService.createGif(images, delayMs, repeat);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=animation.gif")
.contentType(MediaType.IMAGE_GIF)
.body(gifBytes);
}
}
Service 實作
@Service
public class GifService {
public static byte[] createGif(List frames, int delayMs, int repeat) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
encoder.start(baos);
encoder.setRepeat(repeat);
for (BufferedImage frame : frames) {
encoder.setDelay(delayMs);
encoder.addFrame(frame);
}
encoder.finish();
return baos.toByteArray();
}
}
八、常見問題排查
| 問題 | 原因 | 解決 |
| 輸出檔案 0 bytes / 損壞 | 未呼叫 finish() 或串流未關閉 | 確保 finally 區塊呼叫 finish() |
| 動畫播放速度不對 | GIF 延遲單位為 10ms,非 1ms | setDelay(100) = 1 秒,非 100ms |
| 顏色失真/條紋 | 256 色量化損失、抖動未開啟 | setQuality(1); setDither(true); |
| 透明背景變黑/白 | 未設定透明色或來源圖無 Alpha | setTransparent(rgb) 或預處理圖片 |
| 大尺寸 GIF 記憶體溢位 | 幀緩衝佔用過多堆記憶體 | 縮小尺寸、降低幀率、分批寫入臨時檔 |
總結
Java 生成 GIF 動畫核心在於:幀序列準備 → AnimatedGifEncoder 設定參數 → 逐幀加入 → finish() 完成。雖然 GIF 受限於 256 色與單一透明色,但在簡單動畫、載入指示器、文檔演示等場景仍具不可替代的普遍相容性。現代專案建議評估 WebP/APNG 作為優先輸出格式,僅在相容性要求下回退 GIF。掌握編碼器參數調優 (延遲、品質、抖動、透明色) 能在檔案大小與視覺品質間找到最佳平衡點。