GIF 檔案格式
Graphics Interchange Format — 索引色點陣圖與簡易動畫。
- MIME 類型
- image/gif
- 副檔名
- .gif
- 分類
- 圖片
Magic number(檔案簽章)
| 0x00000000 | 474946383961 | GIF89a | 'GIF89a' (or 'GIF87a') |
關於 GIF 格式
GIF 每格最多只有 256 色,但支援循環動畫與 1 位元的透明度,這正是它能一路活到迷因時代的原因。標頭是 ASCII 字串 GIF87a 或 GIF89a,標明規格版本,緊接著是七個位元組的邏輯畫布描述子,交代畫布尺寸並描述全域色盤。之後整個檔案就是一串區塊,每個區塊由單一位元組帶頭:2C 開始一張影像、21 開始一個擴充區塊、3B 結束檔案。像素是拿色盤索引去跑 LZW 壓縮的,每張影像一條變動位元寬的碼流 — 這也是為什麼一段照片轉成的 GIF 通常比它來源的那張 JPEG 還大。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 3 | Signature | 47 49 46,ASCII 的 GIF 三個字母。兩個版本共用,也是偵測器唯一能放心依賴、又不必順便決定要接受哪個規格版本的三個位元組。 |
| 3 | 3 | Version | 38 37 61 代表 87a,38 39 61 代表 89a。這是在宣告檔案後面可以出現哪些區塊類型,不是在講影像資料本身;編碼器照理應該寫下涵蓋自己實際用到的功能的最低版本。 |
| 6 | 2 | Logical screen width | 小端序 16 位元的畫布寬度(像素)。注意位元組順序:GIF 比 PNG 早,數字是反過來存的,所以 40 00 是 64 不是 16384。畫布不見得等於任何一格的尺寸。 |
| 8 | 2 | Logical screen height | 小端序 16 位元的畫布高度。每一格自己會在檔案後面帶上位置與尺寸,而且可能比畫布小很多 — 動畫 GIF 就是靠這點避免重送畫面上沒變的部分。 |
| 10 | 1 | Packed screen descriptor fields | 位元 7 表示後面有全域色盤。位元 6 到 4 是來源的色彩解析度。位元 3 是排序旗標。位元 2 到 0 放 N,色盤的項目數就是 2 的 N 加 1 次方 — 所以常見的值 6 代表 128 色,7 代表滿滿 256 色。 |
| 11 | 1 | Background colour index | 指向全域色盤的索引,用在畫布上沒有任何一格畫到的區域。幾乎所有解碼器都無視它,直接把沒畫到的畫布當成透明,所以同一張 GIF 在某個檢視器裡正常、在另一個裡多出一圈色邊,吵的就是這個位元組。 |
| 12 | 1 | Pixel aspect ratio | 0 代表沒有資訊,實務上每個檔案都是這樣寫。其他任何值 V 表示像素長寬比是 V 加 15 再除以 64 — 非方形像素顯示器留下來的遺物。 |
| 13 | variable | Global colour table | 只有在封裝位元組的位元 7 被設起來時才存在,長度剛好是 3 乘以 2 的 N 加 1 次方個位元組的 RGB 三元組:128 項的色盤就是 384 個位元組。N 弄錯,檔案後面所有位元組位移全部跑掉 — 這是用一個位元組的修改毀掉一張 GIF 最有效率的辦法。 |
| -1 | 1 | Trailer | 單一個 3B 位元組,也就是 ASCII 的分號,結束整個檔案。後面的東西一律被忽略;它不見了則是判斷下載被截斷最快的方法。 |
怎麼跟長得像的格式分開
| 容易搞混的 | 怎麼分 |
|---|---|
| GIF87a/GIF89a | 差別就是位移 3 的三個位元組,標頭裡沒有別的。89a 多了圖形控制擴充 — 也就是帶著每格延遲、處置方式和透明色索引的 21 F9 區塊 — 再加上應用程式擴充。所以會動、或帶有任何透明度的檔案,都必須宣稱自己是 89a;一張不透明的靜態圖則可以老實地宣稱 87a,macOS 的 sips 這類編碼器至今還會這樣寫。 |
| Netscape looping extension | GIF 規格裡沒有任何東西能讓動畫循環。這個行為來自一個應用程式擴充區塊:21 FF 0B 之後跟著 NETSCAPE2.0 這十一個 ASCII 位元組,再接一個子區塊,它的第二和第三個位元組是小端序的循環次數,0 代表永遠。在前幾百個位元組裡找 4E 45 54 53 43 41 50 45:找不到的話,不管編輯器當初給你看的是什麼,這段動畫都只會播一次。 |
| PNG | 89 50 4E 47 對上 47 49 46。它們在「帶透明度的無損索引色影像」這件事上重疊,而那個工作 PNG 完勝:完整 alpha 而不是一個透明索引、每通道最多 16 位元而不是 256 色色盤,壓縮也更好。GIF 唯一剩下的優勢,是不用 video 元素就會動。 |
| WebP | 動畫 WebP 用零頭的位元組做同一件事,而且是 RIFF 檔案:位移 0 是 52 49 46 46、位移 8 是 57 45 42 50,位移 12 有一個設了動畫旗標的 VP8X chunk。現在多數聊天與社群平台會把使用者上傳的 GIF 轉成 WebP 或 MP4,再用原本那個 .gif 網址繼續送出去。 |
常見的壞法
- 截斷。標頭和色盤都完好,所以檢視器知道畫布多大,也會把已經收到的影格一格一格畫出來然後停住。確認的方式是檔案結尾少了 3B;沒收完的最後一格會在 LZW 串流斷掉的地方出現一條純色帶。
- 全域色盤大小寫錯。位移 10 那個位元組的低三個位元決定後面跟著幾個位元組的色盤,所以壞掉半個位元組就會讓第一個區塊的起點偏掉好幾百個位元組,即使檔案裡其他每個位元組都好好的,整份也讀不了。
- LZW 串流失去同步。碼是變動位元寬、而且跨位元組邊界打包的,完全沒有對齊可言,一個位元翻掉就會讓那一格剩下的部分變成斜向雜訊。因為每一格有自己的串流,損壞會停在下一個影像區塊,不會一路傳到動畫結束。
- Netscape 應用程式擴充掉了。用一個不保留未知擴充區塊的工具重新編碼,循環次數就被默默丟掉,本來會一直跑的動畫變成播一次就定格。檔案沒有任何地方不合法,所以不會有工具報錯。
- 處置方式被改寫。每個圖形控制擴充裡那三個位元的 disposal 欄位,決定下一格畫上去之前要不要先清掉前一格。轉檔工具寫錯值,就會產生那種每一格疊在前一格上的拖影 — 一個完全合法、只是動起來不對的檔案。
這個格式現在還在用嗎
GIF 十年前就該退休了,而它哪裡都不會去,理由只有一個:它是唯一一種不需要 video 元素、不需要編碼授權、也不需要播放鍵,就能在每一個算繪器、郵件軟體、聊天視窗和 wiki 裡直接動起來的動態影像格式。就短短一段循環片段來說,它到現在仍是阻力最低的選項 — 即使動畫 WebP 和靜音的 MP4 用不到它幾分之一的大小就能做得更好看。所以現在多數大平台的做法是:照收你上傳的 .gif,背後轉成 WebP 或 H.264,網址上的副檔名留著不動。至於當靜態圖片,它已經完全沒有立場:每格 256 色、透明只有一個全透明的色盤索引,每個面向都輸給 PNG,而當年讓大家避開它的 LZW 專利也早在 2004 年就過期了。它真正還站得住腳的地方是很小的 UI 動畫、載入轉圈,還有不怎麼光彩的那個用途 — 行銷信裡那張一像素的追蹤圖,檔案比包住它的 HTTP 標頭還小。
在這個站上打開 GIF 檔
這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。
相關格式
容易和 GIF 搞混的格式,可以比對它們的開頭位元組。