简短的回答
一个 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、画到屏幕上的帧,还有那份字节账。
这件事在这儿比在多数活儿上更要紧,因为人们最想拆开看的文件,往往正是他们最拿不准要不要拿出去的那些——救回来的东西、别人发来的东西、里面有一段自己还没读过的注释的东西。关于上传文件的那篇更长的说理,在这儿和在本站任何地方一样成立。