⚠️ 这是一个非官方翻译网站,与 ImageMagick Studio LLC 无关。准确信息请参阅原文(https://usage.imagemagick.org/anim_basics/index.html)

ImageMagick 示例 -- 动画基础

ImageMagick 示例:前言与索引
GIF 动画与动画元数据
帧处置方法

GIF 动画与动画元数据

ImageMagick 处理图像列表输出的默认方式是生成多页图像。不过对于 GIF 图像格式,这会采取「GIF 动画」这种特殊形式。 |

  magick -delay 100  -size 100x100 xc:SkyBlue \
          -page +5+10  balloon.gif   -page +35+30 medical.gif  \
          -page +62+50 present.gif   -page +10+55 shading.gif  \
          -loop 0  animation.gif

[IM Output]
这是一个更高级的「闪烁」示例,它使用了一个 shell 脚本「[star_field](../static/img/scripts/star_field) 」。这个脚本是我从生成 随机星空 的实验中发展出来的。 |

  star_field 70x46  stars1.gif
  star_field 70x46  stars2.gif
  star_field 70x46  stars3.gif
  magick rose:  -compose Screen \
          \( -clone 0 stars1.gif -composite \) \
          \( -clone 0 stars2.gif -composite \) \
          \( -clone 0 stars3.gif -composite \) \
          -delete 0 -set delay 25 -layers Optimize rose_sparkle.gif
  rm stars[123].gif

[IM Output]
基本上就是生成三个大小合适的随机星空,然后用「[Screen](compose.html#screen)」alpha 合成将它们叠加到我们的图像——IM 内置的「rose:」——之上,用给定的星星图案让图像变亮。然后把整体通过 IM 的通用 GIF 动画优化器处理。上面的例子可能看起来很复杂,因为它用到了一些我还没有介绍的高级 IM 功能,但结果是一个相对简单却经过良好优化的三帧动画。你也可以看看 扭曲动画 中一些用简单 shell 脚本创建的更复杂动画。有几个专门为 GIF 动画而设的额外 IM 设置,了解它们是踏入 GIF 动画世界的第一步…… [**-dispose**](https://imagemagick.org/command-line-options/#dispose) {method}


| 指定后续图像应对 GIF 动画此前的结果做什么。有效的选项为「Undefined」「[None](#none)」「[Previous](#previous)」「[Background](#background)」。(设置的解释见下文)
[**-loop**](https://imagemagick.org/command-line-options/#loop) {number} | GIF 动画在停止前循环遍历图像序列的次数。这是一个输出时的「图像写入」设置,所以可以设置在命令行的任何位置,不过只有最后一个这样的设置会被使用。通常它默认设置为零(无限循环),但如果读入的任一图像具有不同的值,则该设置会被设为那幅图像的值。因此我建议你在创建 GIF 动画时,总是在读入所有图像之后再设置「[-loop](https://imagemagick.org/command-line-options/#loop)」。更多信息见下文 循环的终点
[**-delay**](https://imagemagick.org/command-line-options/#delay) {time} | 设置在绘制此设置定义之后读入或创建的图像后暂停的时间延迟(以 1/100 秒为单位)。你可以通过指定「x」缩放(以每秒的 tick 数给出)来为时间延迟指定不同的刻度。例如「10x1」是 10 个 1 秒的 tick,而「10x100」是 10 个百分之一秒的 tick。基本上「x」等价于分数中的「/」号。例如指定「1x160」将设置一个适合每秒 160 帧的延迟。 | | GIF 动画的延迟必须以百分之一秒为单位指定才能正确工作,这正是它作为默认时间单位的原因。「x」因子更多用于生成其他更接近影片的格式,例如 MNG 和 AVI。
---|---
[**-set**](https://imagemagick.org/command-line-options/#set) dispose {method} [**-set**](https://imagemagick.org/command-line-options/#set) delay {time}
| 前面那些选项设置会在给出该选项 之后 对新创建或读入的图像设置图像属性,而「[-set](https://imagemagick.org/command-line-options/#set)」选项是一个运算符,它允许你对当前图像序列中 已经 存在的所有图像设置图像属性。这样你就能在图像被加载或修改之后,改变整个动画或仅单个帧的设置。
[**-page**](https://imagemagick.org/command-line-options/#page) {w}x{h}+{x}+{y}
| 这让你设置即将读入的图像的偏移位置。由于这是一个设置选项,它只对该设置之后的图像应用你给出的几何信息。它不影响已经读入内存的图像。如果不给出,或用「[+page](https://imagemagick.org/command-line-options/#page)」关闭,则读入图像的偏移会被保留。如果图像没有偏移,它会被放在「+0+0」,也就是工作画布或「页面」的左上角。通过指定宽度「x」高度,它还可用于定义更大的工作画布。只有序列中第一幅图像的页面宽度和高度设置会用于设定整个 GIF 动画的画布尺寸,其他所有页面尺寸设置在动画最终写出时都会被忽略。当读入一个 GIF 动画时,画布尺寸会被设置到动画中的所有帧上。MNG 动画可以保存帧偏移,但不保存画布尺寸。第一幅图像的尺寸定义了整个动画的画布尺寸。 | | _GIF 图像格式无法为画布上的图像指定负偏移。如果你尝试使用负偏移,IM 会在该图像(或动画帧)写入 GIF 文件时将其重置为零。

大于图像画布的正偏移完全可以接受,但可能导致图像在显示时不出现在画布的绘制区域内。GIF 动画显示程序如何处理这种情况是未定义的。建议谨慎。_
---|---
[**-repage**](https://imagemagick.org/command-line-options/#repage) {w}x{h}+{x}+{y}
| 这与「[-page](https://imagemagick.org/command-line-options/#page)」完全相同,只不过它是图像运算符而非设置。这意味着你可以用它来改变或重置已经读入内存的图像或动画帧的「页面几何信息」。更简单的「[+repage](https://imagemagick.org/command-line-options/#repage)」形式,只是把当前图像序列中每一帧的所有图像的「页面几何信息」重置为零偏移和图像的实际尺寸。当你从动画中提取单独的帧时,这个操作至关重要(见下文 Adjoin 示例)。不过「[+repage](https://imagemagick.org/command-line-options/#repage)」会破坏每幅图像中存储的大量定位信息,因此你或许还应该把这些信息提取到一个单独的文件中以便日后重用。见下文 动画列表信息

重要提示

对于尚未处理完成的中间动画,不要直接保存为 GIF。如果你想通过一系列独立的处理步骤来处理动画,可以使用 IM 的内部格式 MIFF 作为临时文件格式。我再说一遍……

不要把 GIF 用作中间文件格式,改用 MIFF

如果你犯了保存为 GIF 的大错,只会让最终的动画变得更糟,因为此时 IM 会执行一次自动的 颜色量化,以减少存在的颜色数量。不仅如此,它还会对每一帧完全独立于其他所有帧地进行量化,使得任何后续处理、尤其是任何 GIF 优化都变得困难得多。解决这个问题是一个复杂的多层次难题,将在下一节 动画优化 中讨论。


帧处置方法

创建 GIF 动画的人最先遇到麻烦的是「[-dispose](https://imagemagick.org/command-line-options/#dispose)」设置。这并不意外,因为它是一个复杂的设置。更糟的是,很多动画程序,包括许多 Web 浏览器,并不总能正确处理 GIF 处置的元数据设置。然而,使用正确的处置方法,会对动画运行和优化的效果产生很大影响。在 ImageMagick 中首先要记住的是,几乎所有特殊的动画选项都是用于图像读取的设置。也就是说,它们会应用到在给出该设置之后读入的图像上。「[-loop](https://imagemagick.org/command-line-options/#loop)」设置是唯一一个通常在动画完成之后、即将保存动画之前才使用的。「[-dispose](https://imagemagick.org/command-line-options/#dispose)」的基本职责,是定义一幅图像在按其「[-delay](https://imagemagick.org/command-line-options/#delay)」时长显示 之后 应如何被移除。也就是说,你需要在读入该帧的图像 之前 给出图像的「[-dispose](https://imagemagick.org/command-line-options/#dispose)」和「[-delay](https://imagemagick.org/command-line-options/#delay)」设置。但这个动作是在该图像显示之后才应用的。这有点违反直觉,但按照 IM 处理图像的方式来看是说得通的。只要记住这一点,就不会有问题。这些选项的「加号」形式,与 IM 中大多数其他设置一样,会阻止该设置应用到任何正在读入的图像上。这意味着如果你不指定某个设置,帧图像就会继续使用随该图像读入的设置(如果有的话)。当你日后想读入一个 GIF 动画做进一步处理时,这一点可能很重要。或者当把一个 GIF 动画合并到另一个之中时(最困难的动画技术)也是如此。

Dispose None - 各帧按顺序叠加

GIF 动画默认的「[-dispose](https://imagemagick.org/command-line-options/#dispose)」设置是「**Undefined**」,大多数动画程序会把它当作「**None**」处置设置来对待。基本上这是告诉计算机,保留这一特定帧所叠加的一切。或者更确切地说,「什么也不做」。不过请注意,在动画序列的末尾、循环重复之前,整个画布总是会被清除。例如,这是一个标准的「None 处置」动画…… |

  magick -delay 100 -dispose None \
              -page 100x100+5+10  balloon.gif  \
              -page +35+30 medical.gif  \
              -page +62+50 present.gif  \
              -page +10+55 shading.gif  \
          -loop 0  anim_none.gif

[IM Output]
这种处置技术非常适合不涉及任何形式透明度的动画,例如画在纯色或带图案背景上的动画。 |

  magick -dispose none  -delay 100 \
                -size 100x100 xc:SkyBlue +antialias \
                -fill DodgerBlue -draw 'circle 50,50 15,25' \
                -page +5+10  balloon.gif  \
                -page +35+30 medical.gif  \
                -page +62+50 present.gif  \
                -page +10+55 shading.gif  \
          -loop 0  canvas_none.gif

[IM Output]
请注意,这种技术只能向动画添加可见的颜色。它绝不可能真正让动画的任何部分重新变透明。(见下文 叠加动画)。要同时处理透明度,需要使用其他几种处置方法之一。

Dispose Previous - 保留背景画布

**Previous**」处置方法相对简单。当前图像结束时,把画布恢复到该图像被叠加之前的样子。如果前一帧图像也使用了「Previous」处置方法,那么结果就与那一帧之前的样子相同……如此类推……以此类推……例如在这个动画中,后面的每一帧在叠加与该帧关联的图像之前,都会回到最初的第一帧图像,也就是采用了「[None](#none)」处置设置的那一帧。其结果是一个背景画布,上面只在各帧图像持续的时长内叠加了那一帧的图像…… |

  magick -dispose none  -delay 0 \
                -size 100x100 xc:SkyBlue +antialias \
                -fill DodgerBlue -draw 'circle 50,50 15,25' \
          -dispose previous -delay 100 \
                -page +5+10  balloon.gif  \
                -page +35+30 medical.gif  \
                -page +62+50 present.gif  \
                -page +10+55 shading.gif  \
          -loop 0  canvas_prev.gif

[IM Output]
注意第一幅图像使用的「[-dispose](https://imagemagick.org/command-line-options/#dispose)」方法为「[None](#none)」。这很重要,否则「previous」帧会一路回退到第一帧之前存在的原始空画布。另外请注意,我在上面的动画中使用了值为「0」的「[-delay](https://imagemagick.org/command-line-options/#delay)」。这表示在把第一帧叠加到这个「背景画布」上之前不要等待。没有它,你会看到短暂的延迟,只显示画布图像而上面什么都没有。当然,我仍然需要为后面的图像设置更长的「[-delay](https://imagemagick.org/command-line-options/#delay)」,否则它们会一眨眼就出现又消失,还会顺带耗费观看者大量的 CPU 周期。使用「Previous」处置方法在某些 Web 浏览器中可能容易出现轻微的闪烁或停顿,尤其是在较慢的机器上。虽然这种情况如今相当少见,但闪烁本身依然存在,我认为这是一个 bug。更多细节见下文 零延迟帧。很少有动画采用 dispose previous 风格,原因是计算机很难对其进行优化。问题在于,计算机究竟该挑选哪一帧作为背景图像?对我们人类来说,找出最合适的图像很简单,但对计算机来说却很难决定。动画中最适合用作背景的图像甚至可能根本不打算显示,就像当前这个例子一样,因此在该动画的未优化版本中它可能并不存在。

Dispose Background - 清除到背景

前两种「[-dispose](https://imagemagick.org/command-line-options/#dispose)」方法相对简单,而「**Background**」大概是最难理解的。当某一帧的时间延迟结束时,被该帧叠加的区域会被清除。不是整个画布,只是被叠加的那块区域。完成之后,得到的画布就会传递给动画的下一帧,供那一帧的图像叠加。例如这里我们只是用下一帧替换每一帧。 |

  magick -delay 100 -dispose Background \
              -page 100x100+5+10  balloon.gif  \
              -page +35+30 medical.gif  \
              -page +62+50 present.gif  \
              -page +10+55 shading.gif  \
          -loop 0  anim_bgnd.gif

[IM Output]
为了让你看清究竟发生了什么,我们给动画加上一幅初始画布图像,这样你就能看到「Background」实际上是如何把那一帧从动画显示中「处置」掉的。 |

  magick -delay 100 -dispose none \
                -size 100x100 xc:SkyBlue +antialias \
                -fill DodgerBlue -draw 'circle 50,50 15,25' \
          -dispose background \
                -page +5+10  balloon.gif  \
                -page +35+30 medical.gif  \
                -page +62+50 present.gif  \
                -page +10+55 shading.gif  \
          -loop 0  canvas_bgnd.gif

[IM Output]
如你所见,随着每一个叠加的帧被处置,在下一幅图像被叠加之前,那一帧的区域会被清除为透明。这正是这种 GIF 处置方法的重要之处,因为它是 GIF 动画唯一一种无论动画帧历史如何都能清除任意像素的方法。清除像素的另一种方法只有使用「[Previous](#previous)」回到那些像素原本是清除状态的某一帧。但那依赖于了解动画序列的历史,使得计算机更难对其进行优化。 | _有一种看法认为,这种处置不应把叠加区域清除为透明色,而应清除为 GIF 动画中存储的「background」颜色元数据设置。事实上,旧的「Netscape」浏览器(第 2 和第 3 版)正是这么做的。但它同时也没能正确实现「Previous」处置方法。

另一方面,初始画布也应当由格式的「background」颜色来设定,而这同样没有做到。不过所有现代 Web 浏览器都只把最后叠加的区域清除为透明,因此这如今已成为公认的做法,也是 IM 现在所遵循的。_
---|---
| _在 IM 版本 6.2.6-1 之前,IM 的「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」和「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」操作,无法像所有主流 Web 浏览器那样,处理使用「Background」处置来让像素变透明的动画。示例与细节见 动画 Bug

不过,当没有应用或不打算进行像素清除时,这些功能工作正常。现在「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」已修复此问题,并创建了「[-layers](https://imagemagick.org/command-line-options/#layers) [OptimizeFrame](anim_opt.html#optframe)」方法,以取代把「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」用作 GIF 动画帧优化功能的做法。_
---|---


研究动画

在继续讲解 GIF 动画的基础、类型、优化和处理技术之前,我们需要一些研究现有动画的方法。

Identify - 关于动画的信息

动画由打包进每一个单独帧中的大量信息组成。你可以使用默认的 IM「[identify](basics.html#identify)」命令看到其中一些信息。

  magick identify canvas_prev.gif

[IM Text]

| 如果你没有看到上面那样的输出,说明你的 IM 有点旧了,你确实应该把安装的 ImageMagick 升级到最新版本。否则你将错过 IM 在处理和控制 GIF 动画方面的许多新进展。
---|---
如你所见,为第二帧及以后各帧保存的实际图像只有 32x32 像素,但所有帧都位于一个 100x100 像素的「虚拟画布」上,并在那个更大的画布上带有一个「虚拟偏移」。要查看更多存在的各种元数据,你需要使用一些更专门的 百分号转义格式,让 IM 把它输出出来。

  magick identify -format "%f canvas=%Wx%H size=%wx%h offset=%X%Y %D %Tcs\n" \
           canvas_prev.gif

[IM Text]

这清楚地显示了不仅有画布尺寸、图像尺寸和偏移,还有每一个单独帧所使用的处置方法和时间延迟。注意第一帧具有不同的处置方法和时间延迟,这是正确使用后面「Previous」处置方法所必需的。

Adjoin - 把动画拆分成帧

如上所见,如果文件格式允许,ImageMagick 默认会尝试把多幅图像保存到一个文件中。不过正如 写入多图像列表 中所讨论的,IM 允许你使用「[+adjoin](https://imagemagick.org/command-line-options/#adjoin)」设置,告诉它把每幅图像作为单独的独立图像保存到磁盘。例如,这里我们读入其中一个 GIF 动画,并输出动画序列中各个单独的帧图像。

  magick canvas_prev.gif -scene 1 +adjoin  frame_%03d.gif

[IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

如果你去查看上面这些实际的图像,你会发现,尽管大多数 Web 浏览器显示的是一个更大的 100x100 区域,每个子帧都出现在其上。实际上,显示的大多数图像其实只有 32x32 像素,正如上面前面的「identify」命令所示。也就是说,大部分区域只是一块什么都没画的画布,称为图像的「页面几何信息」或「虚拟画布」。动画的第一幅图像定义了那个更大的「画布」,而其他每一帧都在这个更大的画布上定义了一个「偏移」位置。这些额外的信息被保留在由「[+adjoin](https://imagemagick.org/command-line-options/#adjoin)」设置保存的各帧中。因此你可以轻松地重新构建 GIF 动画。不仅每个单独的帧图像中保留了页面信息,任何延迟、循环和 GIF 处置设置也都被保留下来。 这意味着要重建动画,你只需把所有图像读入即可。 |

  magick frame_???.gif  anim_rebuilt.gif

[IM Output]
不过有时你并不想保留这些页面几何信息。例如当你想把单独的帧用于其他项目时。你可以使用「[+repage](https://imagemagick.org/command-line-options/#repage)」选项重置页面尺寸和偏移,去除「虚拟画布」信息,只留下实际的图像。
通常在提取动画子图像时,你一般还会重置图像的延迟和处置设置,以确保它们不干扰编辑和显示。例如这里我去除了不需要的虚拟画布和偏移,并重置了时间延迟和处置。

  magick canvas_prev.gif  +repage  -set delay 0   -set dispose None \
          +adjoin  repage_%03d.gif

[IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

当然,如果你丢弃了那些元数据,你需要某种方式来记录和编辑这些数据。见下文 动画列表信息,其中有一个脚本,既能提取子图像,又能以可用于重建动画的形式保存动画的元数据。

Coalesce - 完整填出各帧

然而,以子帧的形式查看动画,在典型的动画中通常并不太有用。其一,一个高度优化的动画可能由许多非常小的部分组成,却没有任何视觉线索表明它们如何拼合在一起。它还可能带有许多其他为 压缩优化 而添加、用以减小动画整体文件大小的「噪声」。例如,仅仅通过查看动画中一个个单独的子帧,很难弄清这个动画实际上做了什么。

  magick script_k.gif  +repage  +adjoin  script_k_%02d.gif

[IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output] [IM Output]

[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」操作基本上会把图像转换成动画在前一帧被正确 处置、且下一子帧被叠加之后应有的确切样子。也就是说,不再是每一帧只表示相对前一「已处置」帧的叠加改动的动画序列。这个运算符会在每个时刻创建动画的完整视图,有点像真正的胶片,而不是一个动画序列。这样的序列,称为 合并动画,研究、编辑、修改和重新优化都容易得多。例如这里将为我上面展示的同一个「令人困惑」的动画序列生成一张拼贴,但这次我们会对序列进行「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」,好让你看到实际发生的情况。 |

  montage script_k.gif -coalesce \
          -tile x1 -frame 4 -geometry '+2+2' \
          -background none -bordercolor none coalesce_k_montage.gif

[IM Output]

[IM Output]

如你所见,结果就像动画的一段胶片,让你清楚地看到前面那些片段如何拼合成一个手绘的字母「K」。从 IM 版本 6.2.6 起,「magick montage」命令能理解「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」的用法,让你能创建出上面所示那种动画帧的「胶片」式图像。这个版本还包含了对 coalesce 的修复,任何 GIF 动画工作都应至少使用这个版本(或更好的是最新版本)。 下一节示例给出了一种检查动画的更好的拼贴技术。
合并动画 中,合并图像序列 的「[-dispose](https://imagemagick.org/command-line-options/#dispose)」设置其实无关紧要。不过为了让用户放心,「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」运算符会视情况把每一帧的「[-dispose](https://imagemagick.org/command-line-options/#dispose)」设置设为「[None](#none)」或「[Background](#background)」,好让合并后的图像序列仍能正确地播放动画(如上所示)。 _一帧带有「[Background](#background)」处置,意味着下一帧需要清除至少一个或多个像素才能正确显示。

因此,凡是「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」添加了「[Background](#background)」处置的动画,都意味着该动画无法被保存为简单的 叠加动画(见下文)。

从技术上讲,你可以把合并图像序列的所有处置设置都设为「[Background](#background)」或「[Previous](#previous)」,以生成一个 清除帧动画(见下文)。不过并非所有动画都能以那种形式优化得很好。_
---|---
[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」运算符也有一些非动画的用途。这些用途的示例见 Coalesce 与渐进式扁平化

动画帧拼贴 - 「gif_anim_montage」脚本

[+adjoin](https://imagemagick.org/command-line-options/#adjoin)」运算符能让你从动画中提取实际的图像,「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」能让你看到动画呈现出的各帧,但这两种方法都遗漏了大量关于动画的信息。通过对动画图像进行一些非常细致的操作,你可以把各帧显示出来,不仅呈现实际的帧,还呈现这些帧在更大画布上的摆放位置。下面就是一种这样显示动画的方法。

  magick -dispose Background   script_k.gif  -alpha set \
          -compose Copy -bordercolor black -border 1x1 -compose Over \
          -coalesce  -bordercolor none   -frame 4x4+2+2 \
          -bordercolor none -border 2x2 +append  script_k_parts.gif

[IM Output]

在这里你可以清楚地看到动画是如何运作的。每个子帧图像的摆放都是为了在之前所有叠加之上再添加内容。其结果是一幅缓慢生长的图画。每一帧也都比它所摆放其上的「虚拟画布」小得多。我在开发和调试 GIF 动画时经常使用这种显示技术,因此我把它做成了一个 shell 脚本「[gif_anim_montage](../static/img/scripts/gif_anim_montage) 」,并加以扩展,使其还能在动画每一帧的上方列出一些细节。

  gif_anim_montage   script_k.gif   script_k_frames.gif

[IM Output]

注意各帧所用时间的变化,用以停顿,仿佛把笔从纸上抬起并重新落笔。带有可变时间的动画可能是最有趣的一类,但也更难处理,你将在后面的 IM 示例页面中看到。「[gif_anim_montage](../static/img/scripts/gif_anim_montage)」脚本还有一个特殊选项「-u」,它会在下方衬入一份合并动画的半透明副本。这让你看到新的子帧如何修改所显示的动画。

  gif_anim_montage  -u  script_k.gif  script_k_frames.png

[IM Output]

当然,这里有半透明像素,所以需要「PNG」图像格式,或者你也可以使用该脚本还提供的众多「background」选项之一,从而对动画的最终汇总图像使用 GIF 甚至 JPEG 格式。其他选项让你定义要使用的行数或列数,以及设置各种非透明背景,或使用红色方框而非默认的黑色。这个脚本将在接下来的几页 IM 示例中大量使用。欢迎提出建议和意见。

动画列表信息 - 用于构建动画的选项

正如我所指出的,使用「[+adjoin](https://imagemagick.org/command-line-options/#adjoin)」和「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」以及「[+repage](https://imagemagick.org/command-line-options/#repage)」,都是提取和查看 GIF 动画的有用方法。然而它们在此过程中都会破坏关于原始动画的信息。你可以使用带「[-verbose](https://imagemagick.org/command-line-options/#verbose)」选项的 IM「magick identify」命令看到关于取景、时间延迟、帧处置等的这些额外信息。不过我,以及大概大多数其他用户,都觉得这个命令的输出令人不知所措,并不太能直接使用。这时就轮到我写的另一个特殊 shell 脚本登场了。「[gif2anim](../static/img/scripts/gif2anim) 」脚本会分离出动画的各个单独帧,同时还会算出,为了从那些图像重建动画,你确切需要哪些 IM「magick」选项。你可以把「[gif2anim](../static/img/scripts/gif2anim)」看作一个动画反汇编器,用 IM 选项的形式生成动画的摘要。例如,让我们解码一直在用的那个动画示例,恢复出创建它时所用的原始「magick」设置,以及所用的各个单独图像…… |

  gif2anim canvas_prev.gif

[IM Text]

[IM Output]
[IM Output]
[IM Output]
[IM Output]
[IM Output]

默认情况下,「[gif2anim](../static/img/scripts/gif2anim)」脚本会为各个单独图像和「.anim」选项文件使用相同的基本文件名。因此上述命令生成的动画序列文件名为「[canvas_prev.anim](https://usage.imagemagick.org/anim_basics/canvas_prev.anim)」,各个单独的帧图像则从「[canvas_prev_001.gif](../static/img/anim_basics/canvas_prev_001.gif)」到「[canvas_prev_005.gif](../static/img/anim_basics/canvas_prev_005.gif)」。如果你更仔细地查看结果,会发现它确实成功地重建了我最初创建这个 GIF 动画时所用的原始选项(见 Dispose Previous 动画)。另外,虽然对实际生成动画来说并不重要,叠加各帧的尺寸和时间也作为注释列了出来,以便研究。你可以不把结果保存到文件,而是使用「-l」标志把动画序列选项直接列到屏幕上。也就是说,只输出动画序列文件,而不保存它或动画的各个单独帧图像。

  gif2anim -l canvas_prev.gif

给定一个「.anim」文件和各个单独的帧图像,可以使用一个配套脚本「[anim2gif](../static/img/scripts/anim2gif) 」来重建动画。 |

  anim2gif canvas_prev.anim

[IM Output]
[anim2gif](../static/img/scripts/anim2gif)」默认会以「_anim.gif」后缀重新创建 GIF 动画。你可以看到生成的「[canvas_prev_anim.gif](../static/img/anim_basics/canvas_prev_anim.gif)」动画,看起来和运作起来都与原始动画完全一样。这个脚本只是把「动画序列文件」中所用的特殊字符串「BASENAME」替换掉,去除所有注释,然后把剩下的 convert 选项传给「magick」命令。换句话说,它把上面这个文件当作一种带注释的「convert」脚本来处理。之所以使用特殊字符串,是因为这样一来你就能指定与「.anim」文件本身名称不同的基本文件名。这样你就能使用一整套完全不同的帧图像,例如原始图像的修改版本,从旧动画重新创建出一个不同的动画。这是个非常有用的功能,将在更复杂的动画处理中用到。(示例见 并排追加动画)。和「[gif2anim](../static/img/scripts/gif2anim)」一样,「[anim2gif](../static/img/scripts/anim2gif)」脚本也有相当多有用的选项,帮助你处理和修改动画。其中一些选项将在后面用到。例如见 追加动画。此外,由于「.anim」文件是纯文本,你可以用它,拿动画解码出来的图像去调整 GIF 的元数据,例如动画的时间、位置、重复段落,或者向动画添加新的帧和图像。毕竟这正是我最初编写这些脚本的原因,那还是在我参与 IM 示例很久之前。就目前而言,「[gif2anim](../static/img/scripts/gif2anim)」最有用之处在于检查动画序列,看看究竟发生了什么,以及帧与帧之间所应用的时间。

Dispose Images - 各帧的 GIF 处置形态

这个特殊的「[-layers](https://imagemagick.org/command-line-options/#layers)」方法「**Dispose**」显示的是,在时间延迟结束、GIF 处置方法已应用 之后,但在下一帧的图像被叠加 之前,该帧应有的样子。换句话说,这准确地展示了 GIF「[-dispose](https://imagemagick.org/command-line-options/#dispose)」方法设置对帧实际做了什么,让你能确切弄清动画到底哪里出了问题。例如,这是我们三个 处置方法示例动画 在应用了各自的逐帧处置方法之后各自的样子。请记住,这些动画每一个都由一幅设为「None」「[-dispose](https://imagemagick.org/command-line-options/#dispose)」设置的「画布图像」组成,其后跟着四幅更小的图像,它们被叠加后再由各种 GIF 处置方法处置。「[None](#none)」处置动画……

  magick canvas_none.gif -layers Dispose canvas_none_dispose.gif
  gif_anim_montage canvas_none_dispose.gif canvas_none_dispose_frames.gif

[IM Output]

[Previous](#previous)」处置动画……

  magick canvas_prev.gif -layers Dispose canvas_prev_dispose.gif
  gif_anim_montage canvas_prev_dispose.gif canvas_prev_dispose_frames.gif

[IM Output]

[Background](#background)」处置动画……

  magick canvas_bgnd.gif -layers Dispose canvas_bgnd_dispose.gif
  gif_anim_montage canvas_bgnd_dispose.gif canvas_bgnd_dispose_frames.gif

[IM Output]

如果你研究上面的内容,就能确切看到三种 GIF 处置方法各自如何把该帧叠加的图像从动画中清除。注意这三个动画的第一帧总是设为「None」处置,因此保持不变。重要的是后面各帧中处置方法的效果。 -layers Dispose」操作只生成处置各帧的「合并」序列。它本身并不重置处置设置,因此其结果可能无法正确播放动画。要让上面的内容正确播放,请把所有处置方法设为「previous」或「background」,或者你可以在保存之前对动画进行优化。
_在 GIF 处置方法之后最后一帧的样子,对 GIF 动画通常无关紧要,因为在动画重复(循环)之前整个画布会被完全清除。如果它不「循环」而是在动画序列末尾停止,那么最后一帧的处置就不会被应用。

换句话说,如上所示的最后一帧(处置之后)的外观,甚至最后一帧实际的处置设置,对 GIF 动画都不会有任何影响。IM 在对动画进行 帧优化、试图算出合适的处置方法时,通常会把它设为与前一帧相同。_
---|---

Deconstruct - 报告帧间差异区域

在 ImageMagick 中优化动画、使结果更小、下载和播放更快的传统做法,是对其「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」形态执行「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」。如今已不再推荐这样做。你应该改用 通用 GIF 优化器。这个运算符会取一段合并后的图像序列(即动画各帧在显示时实际呈现的样子),把第二帧及以后的图像与前一帧的图像进行比较。然后它用发生了变化的像素的最小矩形区域替换该图像。任何像素变化都会被计入,无论是颜色变化(叠加)还是被清除(擦除)。这相当简单,对于典型的 叠加动画,它会为该动画生成最优的 帧优化。不过 叠加动画 只使用「[None](#none)」处置方法。例如,让我们取上面生成的 coalesce previous 动画,它恰好构成一个 叠加动画,用「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」运算符处理它。 |

  magick canvas_prev.gif   -coalesce     coalesce.gif
  magick coalesce.gif     -deconstruct   deconstruct.gif
  gif_anim_montage  coalesce.gif     coalesce_frames.gif
  gif_anim_montage  deconstruct.gif  deconstruct_frames.gif

[IM Output]
[IM Output]
[IM Output]
如果你还记得,「previous 处置动画」会把每一帧清除到最近一个非 previous 处置的帧,在本例中就是初始的背景画布。如你所见,「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」返回了从一个合并帧到下一个合并帧之间发生变化的区域。由此得到一个经过优化的 叠加动画,它不需要任何特殊的处置设置。这远远比不上我一开始所用的原始手工生成的动画那么优,但其本身仍是有用的。 遗憾的是,「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」完全不理解 GIF 动画的「[-dispose](https://imagemagick.org/command-line-options/#dispose)」设置。因此,如果你在一个会逐帧清除像素的动画上尝试它,例如我们上面创建(并在左侧显示)的「background 处置」动画,它会严重失败。 | [IM Output]
---|---
这里我们取刚才显示的动画,用一个「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」和「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」循环处理它。 |

  magick canvas_bgnd.gif  -coalesce  -deconstruct  deconstruct_erase.gif

[IM Output]
如你所见,「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」慢慢地破坏了动画。基本上,「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」的设计初衷只是找出图像图层之间的差异。它从来不是为正确优化动画而设计的,对于那些需要使用各种处置技术来清除(擦除或变透明)先前叠加像素的动画,它会失败。

帧比较 - 更详细地比较各帧

在 IM v6.2.6-2 中,增加了若干额外的 GIF 帧比较方法。这些方法在内部是正确优化动画所必需的,但被认为足够有用,因而也在命令行和其他 API 接口中提供。

Compare_Any

[-layers](https://imagemagick.org/command-line-options/#layers)」方法「**CompareAny**」其实与「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」完全相同。事实上,「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」运算符只是「CompareAny」方法的一个功能别名。让我们再次看看对「background 处置」动画执行「deconstruct」或「CompareAny」的实际图像结果。

  magick canvas_bgnd.gif  -coalesce  canvas_bgnd_coal.gif
  gif_anim_montage canvas_bgnd_coal.gif canvas_bgnd_coal_frames.gif

  magick canvas_bgnd_coal.gif  -layers CompareAny   magick compare_any.gif
  gif_anim_montage compare_any.gif compare_any_frames.gif

[IM Output]
[IM Output]

如你所见,第二帧及以后的图像,是包含所有发生变化的像素的最小矩形区域,无论那是叠加了一个新的像素颜色,还是把一个旧像素清除为透明。

Compare_Clear

[-layers](https://imagemagick.org/command-line-options/#layers)」方法「**CompareClear**」会显示包含从一帧到下一帧需要清除的所有像素的最小矩形区域。

  magick canvas_bgnd_coal.gif -quiet -layers CompareClear compare_clear.gif
  gif_anim_montage compare_clear.gif compare_clear_frames.gif

[IM Output]

注意,由于第一帧和第二帧之间没有清除任何像素,生成了一个特殊的 缺失图像。「[-quiet](https://imagemagick.org/command-line-options/#quiet)」设置用于告诉 IM 不要对这幅图像给出任何警告。如果后面所有帧都变成「缺失」图像,那么该 GIF 动画从不清除像素,这个动画就可以归类为 叠加动画

Compare_Overlay

最后一种「[-layers](https://imagemagick.org/command-line-options/#layers)」比较方法「**CompareOverlay**」,返回自前一帧以来被叠加(添加或颜色改变,但未清除)的像素区域。

  magick canvas_bgnd_coal.gif  -layers CompareOverlay  magick compare_overlay.gif
  gif_anim_montage compare_overlay.gif compare_overlay_frames.gif

[IM Output]

这类似于 IM 特有的「[ChangeMask](compose.html#changemask)」alpha 合成方法。不过,那种方法只返回改变图像的像素,而非发生变化的矩形区域。另见 透明度优化 [-layers](https://imagemagick.org/command-line-options/#layers)」的比较方法以及「[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)」运算符,都不会查看或修改所用的图像 GIF 处置方法。其结果只是一个图像列表,并不打算把它们本身当作动画使用。
_虽然这些运算符是为处理合并图像序列而设计的,但它们也会接受非合并的图像图层序列,而不产生错误。

在这种情况下,在比较各帧之前,每一帧都会用「[Copy](compose.html#copy)」alpha 合成方法叠加到之前已叠加的各帧之上。这种 alpha 合成方法确保图层中的任何透明度也会被加入目标图像。没有它,上述做法就无法在合并图像序列中找到被清除为透明的像素。

注意,这不同于更常规的「[Over](compose.html#over)」合成方法,「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」运算符会用后者来处理显示 GIF 动画所需的「处置/叠加」循环。

_
---|---


动画的种类

你所遇到的大多数 GIF 动画都可归入几种基本的动画类型。了解这些类型能让你理解动画是如何从一帧显示到下一帧的,也能让你在处理和修改动画时走一些捷径。

合并动画

合并动画 」基本上就是一段图像序列,它展示动画在每次「处置/叠加」循环之后向用户显示时应有的样子。这些图像基本上就是你观看动画真正的「胶片」时会看到的样子。它是任何动画的简化且完全未优化的形态。这个命名约定来自 IM「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」运算符的名字,该运算符用于把 GIF 处置动画转换为未优化的「合并动画」。大多数视频格式(MPEG、AVI 等)其本质上其实也是「合并动画」。不过这些格式往往没有任何透明像素,而且动画各帧之间通常有恒定的时间延迟。而合并后的 GIF 动画可以有透明像素,且时间延迟差异很大,从立即的「0」延迟,到非常快,或非常非常非常慢。合并动画中任何 GIF 处置设置都没有任何意义,不过「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」运算符会适当地设置处置,使得到的图像序列仍能作为有效的 GIF 动画工作。总是把每个像素从一帧替换到下一帧的视频格式,一般只需使用「[None](#none)」或「[Undefined](#none)」GIF 处置设置。下面是一个其本质上也是合并动画的动画示例,加上一份对动画各个单独帧的「[gif_anim_montage](../static/img/scripts/gif_anim_montage)」显示。 [IM Output] [IM Output]
大多数不包含或不使用透明度、且对整个画布进行动画的动画,通常都以合并动画的形式保存和分发。

叠加动画

叠加动画 」是这样一种动画,其中动画的每一帧只向当前显示的动画叠加新的像素。换句话说,在动画的任何时刻,它都不需要把某个像素清除为透明。各个单独的帧可以包含透明度,或作为背景,或作为其优化的一部分,但它从不把某个像素清回透明。当然,如果完全不使用透明度,那么该动画就一定能转换成一个简单的叠加动画。这大概是最简单的一类 帧优化 动画,也是一种无需客户端做任何特殊处理的动画。向用户显示的每一帧,都可以简单地视为之前所有帧的「扁平化」图像。任何只使用「[None](#none)」GIF 处置方法的动画都是「叠加动画」。例如上一个例子不仅是一个「完全合并动画」,也是一个「叠加动画」,尽管并非所有这样的「完全合并动画」都是「叠加动画」。你可以对一个 合并 动画使用「CompareClear」图层方法,检查第二帧及以后的所有图像是否都是「缺失图像」,来测试某个动画能否变成叠加动画。也就是说,从一帧到下一帧没有任何像素需要被清除或擦除。事实上,如果一个动画无需修改就能变成叠加动画,那么 IM「[coalesce](#coalesce)」运算符对所有帧只会使用「[None](#none)」处置方法。若非如此,「[coalesce](#coalesce)」就会对至少一部分帧使用了「[Background](#background)」处置。这就为你提供了另一种检测「仅叠加」能力的方法。叠加动画可以使用透明度,但它们在透明背景上没有任何移动的部件。例如,手绘字母「K」的动画就是一个叠加动画,因为每一部分都只是在透明背景上添加或改变已有的部分。它绝不向最终图像添加新的透明度(作为第一帧的一部分则除外)。 [IM Output] [IM Output]
[IM Output] 这并不是说叠加动画无法处理移动的物体,只是意味着你需要一个非透明的背景,这样你就能在不需要透明度的情况下「擦除」移动部件的旧位置。例如看看这个「把世界下载到文件夹里」动画的各帧……

[IM Output]

当然,需要通过叠加原始背景来「擦除」旧的部分,意味着叠加的子图像通常更大,因此 GIF 动画文件的尺寸通常也更大。遗憾的是,使用 IM 的 帧优化 运算符,在其试图寻找更小 GIF 文件尺寸的过程中,可能、而且极有可能会把一个「合并叠加动画」变成不是「叠加动画」的东西。不过,通过对动画使用 Deconstruct 而非使用 帧优化,你可以确保动画仍是一个简单的「叠加动画」,但前提是该动画确实是一个「叠加动画」。如果动画不是「叠加动画」,那么 Deconstruct 操作可能会严重出错(见上文 Deconstruct)。凭借一些人工技巧,你仍然可以把叠加动画优化得更好,例如使用 拆分帧更新,并应用某种形式的 压缩优化,同时不破坏动画「仅叠加」的要求。「叠加动画」通常完全不显示透明度(它们可以把透明度用作优化的一部分,但不显示它)。而如果不显示任何透明度,那么该动画就一定是「叠加动画」。为什么「叠加动画」如此重要?因为现实中确实存在只支持这种动画类型的软件。它处理起来简单得多,因为只需执行叠加,而无需处理透明度,也无需为处理 GIF 处置方法而保存前一帧。这类软件虽然少见,但确实存在。

清除帧动画

当一个动画只使用「Previous」或「Background」GIF 处置时,你得到的是一种非常特殊的动画类型。注意,如果只使用「Background」处置,动画的所有帧都会先显示,然后在下一帧显示之前被清除。同样,当只使用「[Previous](#previous)」时,动画在下一帧显示之前总是回到初始的已清除画布,产生相同的效果。如果你只使用这两种处置设置的混合,情况也一样。也就是说,只使用这些处置方法的动画,其各帧都是所要显示内容的完整副本。也就是说,该帧包含了在那个时刻将向用户显示的一切。这并不是说该动画就是一个「完全合并动画」。因为子帧可能比动画的虚拟画布区域小得多,但该帧之外的一切都会被视为透明(或背景),不含任何重要内容。例如看看这个奔跑的兔子动画…… [IM Output] [IM Output]
注意,每一个子帧都是所显示的完整图像。不多也不少。还要注意,没有哪一帧实际上需要用到动画的整个虚拟画布。最后请注意,所有帧的处置都设为「[Previous](#previous)」,出于你将在下文看到的原因,这是更合乎逻辑的处置设置。不过所有处置设置本可以设为「[Background](#background)」处置或两者的任意混合,而不改变最终结果。由于没有更好的名字,我把这样的动画称为「清除帧动画 」,但我也见过它被称为「Previous 或 Background 处置动画」。一个动画唯一不属于这种类型的情形,是动画中至少有一个非空白帧使用了「None」或「Undefined」(两者相同)处置方法。这种动画非常特殊,因为它不像 叠加动画,能在动画序列中任何位置处理任意程度的透明度清除。但它也能非常快速地叠加到任何静态背景图像之上。要做到这一点,我们需要把「清除帧动画」的定义稍微收紧。具体来说,我们需要确保所有处置都设为「[Previous](#previous)」(在我们的例子中已经如此)。做到这一点后,你就可以在前面预置一幅图像(带零延迟)来在下方衬入一个背景。 例如,让我们把兔子放到一片草地上……
  magick bunny_grass.gif bunny_anim.gif -loop 0  bunny_on_grass.gif

[IM Output]
如你所见,这种做法如此简单,以至于许多应用程序使用这类 GIF 动画来给更大的对象添加符号或其他指示物(文件锁、笑脸、星星等)。播放这样的 GIF 动画也很容易,因为应用程序只需把该区域清除为某个简单不变的背景图像,然后叠加序列中的下一帧即可。除了那个静态不变的背景显示之外,无需计算处置,也无需跟踪「previous」显示。这也是为什么「[Previous](#previous)」处置是 清除帧动画 首选处置的原因。不像 叠加动画 只是 GIF 动画的一个特殊子集,所有动画都可以保存为 清除帧动画。只需对动画执行 coalesce,并可选地 trim 掉四周任何透明边缘以进行帧优化,然后重置处置。

  magick any_animation.gif -coalesce -trim \
          -set dispose previous   cleared_frame_animation.gif

你甚至可以在那个背景上重新定位动画…… |

  magick bunny_grass.gif \( bunny_anim.gif -repage 0x0+5+15\! \) \
          -loop 0  bunny_on_grass2.gif

[IM Output]
因此,清除帧动画 通常由透明背景上一个不断变化或移动的小对象组成。这些动画可以直接用于网页,或用作动画符号,也可以与其他动画合并以产生复杂得多的动画。总而言之,这种类型的动画很适合用在动画部件的素材库中,供创建更大更复杂的动画之用。
不过,像这样为 GIF 动画添加背景存在一个问题。如果你看前面的示例,大概会注意到快速移动的动画中有一个明显而恼人的停顿。也就是说,当动画循环、背景图像被重新加载时,兔子会消失片刻。(见 零延迟帧 中的说明)不过对于使用这种技术来给显示对象添加动画符号的应用程序来说,这并不是问题,因为它们反正不显示那个「中间」帧。因此,如果你要为 GIF 动画添加一个非透明的背景,一般来说,把简单的 清除帧动画 转换成 叠加动画 是个好主意。也就是把那个背景添加到动画的每一帧上,而不是给出一幅初始画布。你可以通过以下两种方式之一做到这一点:对上面的动画执行 合并,然后删除那个 零延迟 背景帧;或者把原始动画 图层合成 到一个 静态背景 上。例如……

|

  magick bunny_grass.gif \( bunny_anim.gif -repage 0x0+5+15\! \) \
          -coalesce -delete 0 -deconstruct -loop 0  bunny_bgnd.gif
  gif_anim_montage  bunny_bgnd.gif  bunny_bgnd_frames.gif

[IM Output]
[IM Output]

现在所有帧都被同样地良好显示,重置背景时看不到任何停顿。不过该动画现在是一个 叠加动画,其背景由于动画对象的移动而被「重绘」,因此文件尺寸可能会稍大一些。上述结果优化的后续内容见 透明度优化。 | _IM 论坛成员 el_supremo(Pete)贡献了一个 MagickWand 的等价脚本,清除帧叠加到背景的示例

这个例子在 IM 论坛的 在 MagickWand 中创建清除帧 GIF 动画 中也有详细讨论。

_
---|---

混合处置动画 - 多背景动画

没有什么能阻止你在单个 GIF 动画中混用各种处置方法。事实上,给 清除帧动画 添加背景,做的正是这件事。对我们人类来说,混用处置方法并不那么简单,但这样做能让你生成一些非常复杂的动画显示。一般来说,它们是作为对某个特定动画进行自动 帧优化 的一部分而生成的。只要记住,其结果无法像前面那些动画类型那样直接用于特定用途。事实上,如果某些 GIF 处理程序就是无法正确处理「混合处置动画」,也不要惊讶。这其中包括一些现有的 GIF 优化器。「混合处置动画」的一个典型例子,是一个移动的小物体在动画背景中造成某种半永久但暂时静止的变化。球撞上杠杆就是一个例子。

_需要一个简单示例_

同样,涉及在更大的透明显示区域上两个非常小的移动物体的动画,只有通过混用处置技术——让每个物体使用各自单独的动画帧来移动——才能得到良好的优化。

将来,
一个更复杂的、供研究用的动画。
 * 从一块半透明画布开始
 * 运行一小段「previous」处置
 * 留下一帧作为新的「画布」
 * 再来一些 previous 动画
 * 用一个设为新画布的「background」处置擦除那个「添加」
 * 继续回到起点。
这个动画不仅应当彻底测试 IM 的处置方法,
还应测试各种浏览器的处置方法。

如果你愿意贡献这样一个动画的 IM 示例,可以在此留下你的名字和主页链接!


循环的终点 - 动画停止之时

人们通常认为,不让动画永远循环是个好主意,因为只要动画还在播放,客户端机器就得不停地工作。因此,考虑一下你的动画实际循环多少次是个好主意。基本上……

给你的动画加上循环次数上限(如果可行)

对于用作网页顶部徽标的大型动画尤其如此。取决于动画运行的总时长,对大型徽标来说,10 到 30 次循环通常就足够了。你应该为所有这类动画都加上循环次数上限,以体贴你的用户。「[-loop](https://imagemagick.org/command-line-options/#loop)」保存设置为 0,表示永远循环。这是通常所用的做法,对小型动画来说没问题。不过有些浏览器会在动画达到某个内部循环上限(通常是 256 次循环)时强制停止。对 IM 示例,我一般使用「0」的「[-loop](https://imagemagick.org/command-line-options/#loop)」保存设置,即「永远循环」,因为动画可能出现在页面的任何位置。这意味着,从用户加载页面到他们最终看到并阅读某个特定 GIF 动画之间,可能要过一段时间。如果我用了较小的「循环」次数,动画就不再演示它应当演示的内容,从而失去效果。对于顶层引导页上的大型动画,可以使用「[-loop](https://imagemagick.org/command-line-options/#loop)」为「1」,即普通 GIF 动画的默认值。这表示只把动画播放一遍然后停止。这就把我们引向一个至关重要的问题…… 停在哪里? 有些浏览器,比如旧的「Netscape」浏览器,会重新显示动画的第一帧。不过大多数现代浏览器只会停在最后一帧,忽略那一帧无用的 处置 设置。至于某个特定应用程序会怎么做,则取决于该应用程序,因为并没有真正的标准。事实上,就连「loop」元数据的使用本身也是非标准的,只是旧的「Netscape」浏览器实现了它,后来的所有浏览器都照搬而已。正因如此,如果你希望动画停在某个特定的帧上,比如带有你公司名称或徽标的那一帧,那么把那一帧同时做成动画的第一帧和最后一帧是个好主意。不过其中一帧应当被赋予「零延迟」,以免影响动画的整体循环时长。所以,总结一下……

如果限制了循环,就把「停止」帧同时作为第一帧和最后一帧添加。


零延迟中间帧

关于 清除帧动画,我们已经见过使用带「零延迟」的帧。我也用它们来解释过 Previous 与 Background 处置。这些特殊的帧其实比人们大概以为的要常见得多。它们不仅代表了在动画前面预置一块背景「画布」的方法(就像我上面用它们所做的那样),对于一些更复杂的 帧优化 技术,例如 帧倍增拆分帧更新,它们也是必不可少的。另一种(非常古老的、PNG 格式之前的)用法,是让你能创建包含超过 256 色上限的静态 GIF 图像!也就是说,每一帧提供 256 种颜色,下一帧提供下一组 256 种颜色,全都是零延迟且末尾不循环。感谢 TLUL 在讨论 创建未量化的 GIF 中指出这一点。这些「零延迟中间帧 」并不打算向用户显示。它们只是用来在 GIF 图像中制造一些否则无法实现、或比不用它们能得到更好优化的特殊效果。总而言之……

零延迟帧是中间帧,
它们不打算让用户看见。

ImageMagick 不仅会在动画中创建这样的帧,作为其自动「OptimizePlus」的一部分,还提供了用「RemoveZero」图层方法去除它们的途径。要留意它们,因为它们常常会使你对动画的处理变得复杂。好吧,既然它们很重要,那又怎样?因为许多应用程序不喜欢它们,或者会错误地处理它们。它们把「零延迟帧」视为坏事,即便你出于某种原因有意把它们加入动画。下面汇总了我所知道的、或被告知会「做错事」的应用程序…… Gimp | 不会保存「零延迟帧」,它们总是给任何具有零时间延迟的帧加上一个极小的时间延迟。:-(

---|---
FireFox | 会在这类帧上给出轻微的非零停顿。这大概是为了让完全没有时间延迟的动画不至于耗尽计算机所有的 CPU 周期。但即便动画整体上有非零的显示时间,「firefox」仍不放宽这一限制。

Internet Explorer | 有一个 6 厘秒的最小时间延迟,并忽略任何比这更小的延迟。Internet Explorer 第 8 版还有一个问题:如果任何图像帧超出了第一帧所设定的动画边界,它就会失败(立即重启循环)。我会把这归类为一个严重的 bug。 另一方面,ImageMagick 的「RemoveZero」图层方法确实做对了事:如果所有图像都具有「零时间延迟」,它就不会移除任何帧。事实上,如果这个图层方法看到一个完全没有时间延迟的动画,它会给出警告。这把我们引向另一条经验法则……

绝不要保存一个完全没有任何「延迟」的 GIF(循环)动画

这样做是非常糟糕的做法,也正是上面大多数「有 bug」的应用程序之所以那样做、而不是本应那样做的原因。如果你遇到它们,就去向其所有者投诉。也去向应用程序开发者投诉,好让他们正确处理零延迟帧。哪怕这意味着根本不显示那一帧,只是把它们用作显示下一帧的准备。它们毕竟在屏幕上只停留 时间!