簡短的回答
一個 GIF 是一塊畫布、一串往上畫的長方形,外加一張調色盤,說明那些長方形裡的數字各代表什麼顏色。每個長方形帶著三樣東西:它該停留多久、之後該拿它怎麼辦,以及可有可無的一張自己的調色盤。
人們覺得這個格式奇怪的地方,幾乎都出自那串長方形。你的 GIF 大得離譜,要麼是那些長方形每次都是整塊畫布,要麼是裡面塞了三百張調色盤。它播得太慢,是因為延時低到了任何瀏覽器都不肯往下走的那條地板以下。它拖出尾巴,那是那個叫處置的欄位。
想知道某個具體檔案屬於哪一種,開啟 GIF 分析把它拖進去。這一頁剩下的部分,講的是那些數字都是什麼意思。
幀是長方形,不是畫
只見過 GIF 播出來的人,在這裡最容易吃驚。一幀並不是那一刻動畫的畫面。它是一個長方形,有自己的位置和自己的大小,畫在前面幾幀留下的東西之上。
那個長方形可以是整塊畫布,而在做得糟的檔案裡它每次都是。但 GIF 是允許只存下自上一幀起變過的那部分的——而在畫面大半不動的地方,這就是一個 12 MB 的檔案和一個 900 KB 的檔案之間的差別。一段幾乎靜止的視窗錄屏能做得很小,而同一段錄屏從一個馬虎的轉換器裡出來就不小,原因也在這裡。
光看動畫,你分不出自己手上是哪一種,兩者看起來一模一樣。唯一的辦法是看每一幀存了什麼,而分析器專為此備了一個檢視:把它切到每幀實際存的東西,你要麼看到透明底上一排小形狀,說明編碼器做了它該做的事;要麼一遍又一遍看到整張畫面,說明它沒做。
這個格式裡任何地方都沒有運動補償。不會有東西被存成「和上次一樣,只是往左挪了四個畫素」,影片編碼器才那麼做。變化區域這個招數是 GIF 僅有的一點省法,而它非常值錢。
延時,以及每個瀏覽器都強推的那條地板
每一幀都存著自己要停多久,單位是百分之一秒。這個格式只有這一種單位,所以一個檔案能要的最快是 0.01 秒——每秒一百幀——最長約 655 秒。
它不會得到每秒一百幀。每個瀏覽器都把低於 0.02 秒的延時抬到 0.10。這條規矩 1996 年寫進了 Netscape Navigator,為的是那個年代旋轉的地球儀和會動的「施工中」牌子,之後每個瀏覽器都照抄了下來。從來沒人刪掉它,也不會有人刪。
所以一個每幀都寫著 0.01 秒的 GIF,播出來是每秒十幀,不是一百幀。它比造它的那個程式打算的慢十倍,而檔案對此毫無提示:裡面的延時正是當初要求的值。這是這個格式裡最常見的意外,也是分析器要報兩個時長的原因——檔案說的那個,和瀏覽器真正會照辦的那個。
無論檔案是在哪裡造的,解法都是寫 0.02 而不是 0.01。那是每秒 50 幀,也是真正的上限,比任何用途需要的都快。實際上 0.05 秒——每秒二十幀——差不多就是值得去要的最快了。
延時還能告訴你另一件事。要是它們全都一樣,這個檔案是由一組固定幀率的幀做出來的。要是它們參差不齊——這裡 0.04,那裡 0.11——那是某個東西轉了一段影片、丟掉了一些幀,再把鄰居拉長去補上窟窿。而要是最後一幀比其餘的長得多,那是有意為之:讓動畫在循環回去之前停一下,就是這麼做的。
處置:決定它會不會拖尾巴的欄位
每一幀都寫明自己的時間到了之後,螢幕上該留下什麼。可能的答案有四個,值得知道,因為動畫看起來不對的四種方式裡,有三種就是這個欄位填錯了。
- 留在原地。下一幀直接蓋上去。當各幀不透明且彼此完全遮住時,這是對的,而且最省,因為什麼都不用清。
- 清回背景。這一幀的長方形在下一幀落下前被擦掉。透明就需要這個:沒有它,下一幀透出來的地方會露出前一幀,一組各自獨立的畫面就變成了一疊疊影。
- 還原下面原有的東西。這一幀畫上去之前畫布上的樣子被放回來。一個小東西在靜止背景上移動,就是這麼存的——每一幀畫那個東西,然後背景回來,而寫進檔案的自始至終只有那個東西的長方形。
- 未指定。檔案沒說。所有看圖程式都當成「留在原地」,通常沒錯,偶爾就是透明 GIF 拖出尾巴的原因。
有一處規範和現實分了家。「清回背景」指的是檔案頭裡寫著的那個背景色,而每個瀏覽器都無視它,改成清成透明。這樣已經二十五年了。一個指望那個背景色會出現的檔案,在做它的人、用做它的那個軟體看來是對的,在別處一律是錯的。
調色盤,以及它們要的 768 位元組
GIF 的一個畫素不是顏色。它是一個數字,指向一張最多 256 色的表,每種顏色佔三個位元組。所以一張滿的表是 768 位元組,而一個檔案可以讓所有幀共用一張,也可以一幀一張,或者兩者都有。
兩種安排都合法,只是換來的東西不同:
- 共用一張表,整個檔案只花 768 位元組,而且顏色在幀與幀之間是穩的。GIF 的閃爍——從影片做出來的檔案上那種討厭的抖動——很多時候不過是調色盤在一幀一幀地跳。
- 每幀一張表,讓每一幀都能用上共用表裡沒有的顏色,畫面整個換掉的時候這很要緊。代價是每次 768 位元組。一段 300 幀的動畫,那就是在存下第一個畫素之前,先花掉 230 KB 的調色盤。
還有一筆更安靜的開銷。調色盤的長度必須是 2 的冪,所以用九種顏色的幀照樣拿到十六格的表,用 130 種的照樣拿到 256 格。一定程度的往上湊是躲不掉的。但要是一個檔案的表裡宣告瞭五千種它的畫素從沒引用過的顏色,那就是另一回事了:那是為另一張畫配的調色盤,而最終進到幀裡的並不是那張畫。分析器會把沒用上的格子標出來,讓這個形狀一眼可見。
位元組實際去了哪裡
GIF 的每個位元組都落在為數不多的幾個地方,在斷定一個檔案太大之前,值得知道這幾個地方是什麼。
- 壓縮過的畫素。健康的檔案裡,幾乎全是它。畫面本身,過了一遍 LZW——1984 年的一種壓縮方案,當初是為電子表格的截圖設計的,所以它在大片純色上表現好,在照片上表現差。
- 調色盤。如上,一張滿表 768 位元組。
- 每幀的頭。每一幀八個位元組的時間資訊加十一個位元組的描述符。正常檔案裡不算什麼;兩千個極小幀的動畫裡,是 38 KB。
- 分塊的外殼。壓縮資料被切成最多 255 位元組一段,每段前面放一個長度位元組。大約每 256 個位元組裡有一個,躲不掉,值得看一眼,因為除此之外它完全看不見。
- 後設資料。註釋、色彩配置檔案和 XMP 包。真正荒唐的結果就出在這一項:一個影像編輯器可能留下 40 KB 的 XML,描述的是幾年前做過的一次修改,而在一個小 GIF 上那就是檔案的大半。沒有任何看圖程式會把它畫出來。
要看這張表而不是猜,是因為答案因檔案而異,而解法直接從答案裡長出來。一個 95 % 都是壓縮畫素的檔案,就是畫面本身多,只有減幀、縮小尺寸或者減顏色才有用。一個 30 % 是調色盤、或者 40 % 是 XMP 的檔案,問題便宜得多。
循環並不屬於這個格式
GIF 規範裡沒有任何欄位說動畫會重複。循環來自 Netscape 在 1995 年發明的一個塊——一個裡面寫著 NETSCAPE2.0 的「應用程式擴充區塊」——所有人還是都實現了它,如今網際網路上每一個動圖 GIF 裡都有它。
這也意味著,沒有這個塊的檔案在每個瀏覽器裡都是恰好播一遍就停,在做它的人看來像是壞了。要是一段動畫只走一遍,就是這個塊不見了;這是最值得先查的幾件事之一,而且在任何看圖程式裡都看不見。
這個塊也能寫一個次數——播五遍然後停。零表示永遠,而幾乎所有檔案寫的都是零。
GIF 還能捎帶的別的東西
有三種塊不裝畫面,而且所有看圖程式都跳過:
- 註釋。自由文字,通常是寫這個檔案的軟體的名字,偶爾是作者本來不會選擇公開的東西。沒有東西會顯示它,而這個檔案的每一份複製都帶著它。
- XMP。Adobe 的 XML 後設資料:什麼改過這個檔案、什麼時候改的,有時還有誰改的。它末尾拖著 258 位元組的魔法尾巴,那是為了讓塊長度算得過去的把戲,也是為什麼老老實實去讀會讀出滿屏二進位。
- 純文字。1989 年規範裡的一個塊,要求看圖程式按格子在畫面上寫字。從來沒有任何東西實現過它。檔案裡要是有一個,它寫的內容不會出現。
這三樣在把檔案發出去之前都值得知道:它們是一個 GIF 裡可能透露你的部分,而且除非有東西專門把它們剝掉,否則每一次複製、每一次重新上傳,它們都活著。
讀一個損壞的檔案
GIF 會被截斷——中途停掉的下載、從快壞的硬碟上救回來的檔案、某個程式只寫了一半的東西。因為這個格式是一串塊,而不是一整個帶索引的結構,被截斷的 GIF 通常仍然能讀到它斷掉的那一點為止:斷口之前的每一幀都是完整無損的。
這值得知道,因為大多數軟體會乾脆拒收這個檔案。一個儘量往下讀、並且告訴你在哪裡停住的分析器,至少能讓你知道有多少活了下來,以及丟掉的是一幀還是最後兩百幀。
反過來的毛病也存在:坐在檔案結束標記之後的那些位元組。每個解碼器都在那個標記處停下,所以它們從不會被讀到、也不會被畫出來,而它們通常是某次出錯時被接在第一個檔案後面的第二個檔案。它們是純粹的重量,切掉不損失任何東西。
這些都不需要上傳
讀一個 GIF 的結構不是什麼費勁的工作——走一遍塊的清單,再加一個小小的解壓器——而且從來沒有任何技術上的理由,非要把檔案送到伺服器上去做它。這裡的 GIF 分析把整件事都放在頁面裡做完:走塊、LZW、畫到螢幕上的幀,還有那份位元組帳。
這件事在這裡比在多數工作上更要緊,因為人們最想拆開看的檔案,往往正是他們最拿不準要不要拿出去的那些——救回來的東西、別人發來的東西、裡面有一段自己還沒讀過的註釋的東西。關於上傳檔案的那篇更長的說理,在這裡和在本站任何地方一樣成立。