ByteScope

GIF 檔案格式

Graphics Interchange Format — 索引色點陣圖與簡易動畫。

MIME 類型
image/gif
副檔名
.gif
分類
圖片

Magic number(檔案簽章)

0x00000000474946383961GIF89a'GIF89a' (or 'GIF87a')

關於 GIF 格式

GIF 每格最多只有 256 色,但支援循環動畫與 1 位元的透明度,這正是它能一路活到迷因時代的原因。標頭是 ASCII 字串 GIF87a 或 GIF89a,標明規格版本,緊接著是七個位元組的邏輯畫布描述子,交代畫布尺寸並描述全域色盤。之後整個檔案就是一串區塊,每個區塊由單一位元組帶頭:2C 開始一張影像、21 開始一個擴充區塊、3B 結束檔案。像素是拿色盤索引去跑 LZW 壓縮的,每張影像一條變動位元寬的碼流 — 這也是為什麼一段照片轉成的 GIF 通常比它來源的那張 JPEG 還大。

檔頭結構,一個位元組一個位元組看

位移長度欄位意義
03Signature47 49 46,ASCII 的 GIF 三個字母。兩個版本共用,也是偵測器唯一能放心依賴、又不必順便決定要接受哪個規格版本的三個位元組。
33Version38 37 61 代表 87a,38 39 61 代表 89a。這是在宣告檔案後面可以出現哪些區塊類型,不是在講影像資料本身;編碼器照理應該寫下涵蓋自己實際用到的功能的最低版本。
62Logical screen width小端序 16 位元的畫布寬度(像素)。注意位元組順序:GIF 比 PNG 早,數字是反過來存的,所以 40 00 是 64 不是 16384。畫布不見得等於任何一格的尺寸。
82Logical screen height小端序 16 位元的畫布高度。每一格自己會在檔案後面帶上位置與尺寸,而且可能比畫布小很多 — 動畫 GIF 就是靠這點避免重送畫面上沒變的部分。
101Packed screen descriptor fields位元 7 表示後面有全域色盤。位元 6 到 4 是來源的色彩解析度。位元 3 是排序旗標。位元 2 到 0 放 N,色盤的項目數就是 2 的 N 加 1 次方 — 所以常見的值 6 代表 128 色,7 代表滿滿 256 色。
111Background colour index指向全域色盤的索引,用在畫布上沒有任何一格畫到的區域。幾乎所有解碼器都無視它,直接把沒畫到的畫布當成透明,所以同一張 GIF 在某個檢視器裡正常、在另一個裡多出一圈色邊,吵的就是這個位元組。
121Pixel aspect ratio0 代表沒有資訊,實務上每個檔案都是這樣寫。其他任何值 V 表示像素長寬比是 V 加 15 再除以 64 — 非方形像素顯示器留下來的遺物。
13variableGlobal colour table只有在封裝位元組的位元 7 被設起來時才存在,長度剛好是 3 乘以 2 的 N 加 1 次方個位元組的 RGB 三元組:128 項的色盤就是 384 個位元組。N 弄錯,檔案後面所有位元組位移全部跑掉 — 這是用一個位元組的修改毀掉一張 GIF 最有效率的辦法。
-11Trailer單一個 3B 位元組,也就是 ASCII 的分號,結束整個檔案。後面的東西一律被忽略;它不見了則是判斷下載被截斷最快的方法。

怎麼跟長得像的格式分開

容易搞混的怎麼分
GIF87a/GIF89a差別就是位移 3 的三個位元組,標頭裡沒有別的。89a 多了圖形控制擴充 — 也就是帶著每格延遲、處置方式和透明色索引的 21 F9 區塊 — 再加上應用程式擴充。所以會動、或帶有任何透明度的檔案,都必須宣稱自己是 89a;一張不透明的靜態圖則可以老實地宣稱 87a,macOS 的 sips 這類編碼器至今還會這樣寫。
Netscape looping extensionGIF 規格裡沒有任何東西能讓動畫循環。這個行為來自一個應用程式擴充區塊:21 FF 0B 之後跟著 NETSCAPE2.0 這十一個 ASCII 位元組,再接一個子區塊,它的第二和第三個位元組是小端序的循環次數,0 代表永遠。在前幾百個位元組裡找 4E 45 54 53 43 41 50 45:找不到的話,不管編輯器當初給你看的是什麼,這段動畫都只會播一次。
PNG89 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 搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
PNG89 50 4E 47 0D 0A.png
WebP52 49 46 46.webp
JPEGFF D8 FF.jpg

常見問題

檔案的 magic number 是什麼?

檔案開頭的前幾個位元組,不管檔名寫什麼,它標明的是真正的格式。PNG 一定以 89 50 4E 47 開頭,ZIP 則是 50 4B 03 04。作業系統、瀏覽器和 file 指令讀的都是這幾個位元組,而不是相信副檔名 — 這就是為什麼把檔案改名並不會改變它實際上是什麼。

我要怎麼自己檢查 magic number?

在 macOS 或 Linux 上,xxd -l 16 檔名 會印出前 16 個位元組;在 Windows 上,PowerShell 的 Format-Hex -Path 檔名 -Count 16。或者把檔案丟進本站的檔案類型辨識工具,它會在本機讀取位元組並告訴你是什麼格式,不會上傳任何東西。

為什麼兩種格式的 magic number 會一樣?

因為其中一種是建立在另一種之上。docx、xlsx、pptx、apk、jar 與 epub 骨子裡都是 ZIP 壓縮檔,所以全都以 ZIP 的簽章開頭;要分辨它們得看裡面的項目。Java class 檔和 macOS 的通用二進位檔都以 CA FE BA BE 開頭,靠緊接其後的欄位區分。

有完全沒有 magic number 的檔案嗎?

有。XML、HTML、JSON、SVG、CSV 這類以文字為基礎的格式沒有固定的位元組簽章,所以改用結構來辨識:開頭的 <?xml 宣告、<svg 元素,或是 { 後面接著帶引號的鍵。這種偵測是合理推測而非確定,因此本參考頁會把這些格式標示為結構偵測。

magic number 相符就代表檔案安全嗎?

不代表。它告訴你的是格式,不是內容是否無害 — 一個貨真價實的 PDF 或 docx 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

手上有檔案可能是 GIF?用檔案類型辨識工具查出來 — 它在本機讀取 magic number,不上傳任何東西。或者瀏覽所有格式。