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

ImageMagick 示例 -- 动画优化

ImageMagick 示例 前言与索引
优化导论
ImageMagick 的通用 GIF 优化器
帧优化

动画优化导论

优化动画并不容易,尤其是 GIF 动画:它有颜色限制,还能选择不同的帧处置技术,并可以在一帧到下一帧之间使用更小的 '子帧' 叠加。优化动画时,你应当按以下顺序尝试进行优化。

不过,我们讲解这些优化技术的顺序并非如此。对于 GIF 动画,帧优化是最基本的优化技术,也是收效最大的地方。因此会最先介绍它。用户最感头疼的优化,大概是由 GIF 动画的颜色限制所导致的颜色优化。其中一个方面——单一全局颜色表——必须作为保存为 GIF 之前的最后一步来完成,否则各操作符对最终 GIF 文件保存的效果可能会丢失。


ImageMagick 的通用 GIF 优化器

"[-layers](https://imagemagick.org/command-line-options/#layers)" 方法 '**Optimize**' 会使用下面将详细讨论的若干技术,试图用一个合理的步骤来优化 GIF 动画。目前这个选项等同于(按顺序)……

到这一步你就可以立刻保存 GIF 动画了。这些都是相当安全的优化步骤,可应用于大多数动画序列,然而并不保证一定能得到更小的 GIF 动画。对于原始视频序列尤其如此,因为对它做透明度优化通常会使 LZW 压缩率变差。不过对于大多数涉及卡通类图像的 GIF 动画,'Optimize' 操作符应当能生成一个不错的、优化良好的动画。该操作符仍在开发中,将来很可能还会加入更多标准的优化步骤,例如……

  • 对 alpha 通道做 50% 阈值处理,就像 IM 在保存为 GIF 文件格式时通常所做的那样,以去除半透明像素。如果你愿意,仍可以事先自己处理半透明,以覆盖这一行为。详见 GIF 布尔透明度
  • 某种颜色优化技术。具体是哪一种尚未确定,可能会根据动画及其涉及的颜色数量来选择。欢迎提出建议
  • 一个单一全局颜色表,即 "[+map](https://imagemagick.org/command-line-options/#map)" 操作。

换言之,我们希望 'Optimize' 最终能成为 IM 通用的 GIF 动画优化器,供 IM 用户快速便捷地使用。在此之前请谨慎使用它,尤其是在脚本中,因为它会发生变化。当然,对某个特定动画而言,许多优化步骤可能并不值得费这个力气。这个选项也很可能会变得相当慢。这就是本 IM 示例章节所着眼的规划与目标。


帧优化

帧优化的基础,是叠加一张更小的子图像,而不是完整地叠加整幅图像。这显然会产生更少的像素,从而在磁盘上得到更小的文件,或在网络上传输更少的数据。此外,叠加更小的帧还意味着客户端计算机在屏幕上更改像素的工作量也更少。然而 GIF 格式提供了不同的处置方法来处理上一次显示的帧,这会导致不同大小的叠加。不仅如此,还可以把叠加拆分为多个部分或更新动作,从而得到更复杂但更优化的动画。由于进行帧优化很复杂,通常总是先用 "[-coalesce](https://imagemagick.org/command-line-options/#coalesce)" 操作把任何已有的帧优化去掉。参见 Coalesce 示例。当然,这也意味着任何可能存在的手工优化也会被去掉,因此需要一些谨慎。

基本的帧优化

"[-deconstruct](https://imagemagick.org/command-line-options/#deconstruct)" 方法会为 GIF 动画生成一个基本的帧优化。然而正如上一节的 Deconstruct 示例所示,当涉及透明像素时,这个操作符并不能对所有 GIF 动画正常工作。具体来说,就是动画把任何有色像素清除为透明的情况。也就是说,它只对叠加动画有效。"[-layers](https://imagemagick.org/command-line-options/#layers)" 方法 '**OptimizeFrame**' 被设计为一个 GIF 帧优化器,它会使用任意 GIF 处置方法,试图找出最小的子帧叠加图像。其结果通常是一个混合处置动画,不过如果对某个特定动画判断那样最好,它也常会生成清除帧动画纯叠加动画。请记住,输入动画必须是一个 '合并后的动画',即由一系列完整的图像帧组成、全部大小相同、没有任何画布偏移。当然,合并后动画中任何已有的处置方法都完全无关紧要,会被 'OptimizeFrame' 方法忽略。例如,我们用上一节创建的Previous 处置动画来试一下。 | |

  magick canvas_prev.gif -coalesce  -layers OptimizeFrame  optframe.gif
  gif_anim_montage optframe.gif optframe_frames.gif

[IM Output]
[IM Output]
[IM Output]
如你所见,"[-layers](https://imagemagick.org/command-line-options/#layers) OptimizeFrame' 正确地使用 Previous 处置,把我们的动画还原为它原来的帧优化形式。这种优化即便对更棘手的Background 处置动画也同样有效…… | |

  magick canvas_bgnd.gif -coalesce  -layers OptimizeFrame  optframe_bgnd.gif
  gif_anim_montage optframe_bgnd.gif optframe_bgnd_frames.gif

[IM Output]
[IM Output]
这个动画使用 Background 处置得到了完美的帧优化。这个操作符对所有 GIF 动画都能正确工作,通常会返回可能达到的最佳简单 '处置与帧优化'。
现在说说任何简单帧优化(比如 IM 所提供的那种)的坏消息……虽然 '[OptimizeFrame](#optframe)' 会为给定动画返回 IM 所能得出的最佳帧优化,但仍有一些特殊情况它表现不佳。这些情况包括……

  • 需要清除像素(返回透明)、但帧叠加又太大以至于无法高效清除那一小片需要清除的像素的动画(参见下面的移动孔洞动画)。
  • 涉及两处或更多相距较远的小片变化区域的动画。这类动画其实相当常见,而且极难做帧优化。(参见下面的拆分帧更新
  • 拥有非常复杂、长时间(超过 3 帧)保持静止、然后略有变化又再次长时间保持静止……如此反复的背景的动画。或者一个静止背景在极短时间内被大面积遮挡。在这种复杂情形下,任何计算机算法都几乎不可能算出 '最佳' 的帧优化(即:什么才应当被视为静止背景?)。只有凭借对所见之物的直观把握的人类,才能在这些情况下生成一个良好的、最优的帧叠加序列。

征集难以优化的动画示例,欢迎投稿。 如果你找到一个 IM 无法生成良好优化的动画示例,请把它寄给我以供进一步研究。新技术乃至可能的自动解决方案正是这样发展出来的。我自然会把你的名字作为贡献者公布。

不叠加任何像素 - 每隔一帧重复同一图像

[animation] 有时对一幅图像最好的优化就是根本不叠加任何像素!例如右边是一个简单的动画,由 nixscripter 贡献。如果我们看它的各帧,会发现它并不怎么优化。但请注意,动画中每隔一帧就只是简单地重复了一次。

  gif_anim_montage paddleball.gif paddleball_frames.gif

[IM Output]

对它进行帧优化后,我们得到一个非常特别的 GIF 处置序列。 | |

  magick paddleball.gif -coalesce -layers OptimizeFrame  paddleball_opt.gif
  gif_anim_montage paddleball_opt.gif paddleball_opt_frames.gif

[IM Output]
[IM Output]
这里发生的是,IM 没有叠加原始帧,而是选择用 'Previous GIF 处置' 来恢复第一幅图像。由于恢复出的那一帧原样保留,就没有任何变化的像素。因此子帧叠加被缩减到了无。遗憾的是,IM 和 GIF 格式都不允许你拥有尺寸为零的图像,于是改用了一张特殊的、仅含一个透明像素的最小图像。这种图像被称为缺失图像,因为当 "[-crop](https://imagemagick.org/command-line-options/#crop)" '错过' 了实际的图像数据、产生同样结果时,它也被广泛使用。这种图像实际上只保留帧的元数据,例如:处置方法时间延迟循环次数。因此,尽管它是 '空' 的,它仍是动画不可或缺的一部分。于是,通过叠加一张极简的单个透明像素,IM 在这个动画上节省了大量空间(和时间)。

移动孔洞动画 - 难以进行帧优化

这里有一个用任何常规优化方法都很难做好帧优化的 GIF 动画极端案例。这个动画基本上由一张不变的背景图像组成,但背景上有一个透明的 '孔洞',其位置逐帧改变。要创建它,我需要制作一个合并的图像序列:用图层 Alpha 合成在固定背景图像上挖出一个孔洞。我还使用了 "[+antialias](https://imagemagick.org/command-line-options/#antialias)" 开关,以确保只用到四种颜色:三种蓝色加上透明。这样我们就不必处理颜色优化问题。 | |

  magick +antialias -size 100x100 -delay 100 xc:SkyBlue -loop 0 \
          -fill DodgerBlue -draw 'circle 50,50 15,25' \
          -fill RoyalBlue  -draw 'circle 50,50 30,25' \
          null: \( -size 100x100 xc:none -draw 'circle 40,25 27,22' \) \
                \( +clone -rotate 90 \) \( +clone -rotate 90 \) \
                \( +clone -rotate 90 \) -compose DstOut -layers Composite \
          -set dispose background  moving_hole.gif
  gif_anim_montage moving_hole.gif moving_hole_frames.gif

[IM Output]
[IM Output]
如你所见,这个动画能正常工作,一个圆形 '孔洞' 显露出本页的背景色,生成了一个 [IM Text] 字节大小的动画文件。那么我们就对这个动画做一次直截了当的帧优化试试。 | |

  magick moving_hole.gif  -layers OptimizeFrame  moving_hole_opt.gif
  gif_anim_montage moving_hole_opt.gif moving_hole_opt_frames.gif

[IM Output]
[IM Output]
等等,什么也没发生!IM 所能达到的最佳优化竟然是毫无变化!上面这个合并版的动画,难道就是它最优的形式吗?嗯,就这个动画本身而言……是的,这确实是纯粹靠帧处置优化所能达到的最佳简单优化了!不太理想。问题在于,GIF 动画若要 '清除' 或 '擦除' 前面各帧绘制的像素,就需要使用 'Background' 处置方法。不过在某些特殊情况下也可以使用 'Previous' 处置方法。然而 'Background' 处置只能清除刚刚被叠加的区域。由于第一帧是对整幅图像的完整叠加,整幅图像都会被清除。尽管只有动画中一小块区域的像素需要被清除。结果就是,第二帧整帧都需要被叠加,尽管那一帧的大部分刚刚才显示过!这种糟糕的两难局面一直贯穿动画其余的部分,从而产生不了任何基本的帧优化。我早就说过这个动画很难做帧优化。

帧的二重化 - 一种对 '孔洞' 做帧优化的方法

不过并非无计可施。通过给动画添加一些额外的帧,你可以给 '[OptimizeFrame](#optframe)' 方法一些余地,让它更好地利用可用的 GIF 处置方法。例如这里我们添加一个额外的帧:把第一幅图像加倍复制一份,但给它零时间延迟,以免改变动画的整体时序。 | |

  magick moving_hole.gif[0] -set delay 0   moving_hole.gif \
          -layers OptimizeFrame    moving_hole_dup.gif
  gif_anim_montage moving_hole_dup.gif moving_hole_dup_frames.gif

[IM Output]
[IM Output]
通过把第一帧加倍,这个动画现在从 [IM Text] 字节减小到了 [IM Text] 字节。因此,虽然动画现在有五帧,但整体上却小了很多,这要归功于子帧图像叠加尺寸的大幅缩减。二重化实质上把处置方法的像素清除功能,与下一帧执行的像素叠加功能分离开来。在 GIF 动画程序中,处置和叠加都是作为同一次帧更新的一部分完成的,所以速度或质量都不会有可察觉的损失。这是一项复杂而巧妙的技术,很少被 GIF 动画设计者或 GIF 优化程序看到或理解,但当需要它时,它带来的好处很值得。然而子帧图像尺寸的缩减只能维持一小段时间,因为后面的帧也不得不清除像素以便迎接下一帧,于是这些帧又会为了继续清除后续像素而重新变大。也就是说,因为清除像素总是导致更大的帧,而绝不会更小。那么我们就试着把 所有 帧都加倍(除了最后一帧,它永远不需要加倍),看看这对最终图像有何影响…… | |

  magick moving_hole.gif  \( -clone 0--1 -set delay 0 \) \
          +delete -insert 2 -insert 1 -insert 0 \
          -layers OptimizeFrame  moving_hole_double.gif
  gif_anim_montage x2 moving_hole_double.gif moving_hole_double_frames.gif

[IM Output]
[IM Output]
如你所见,虽然帧数几乎翻了一倍,但所有图像的尺寸都小了很多,生成了一个 [IM Text] 字节大小的动画,结果更小,不过远不及我们最初那次单帧二重化的节省幅度。为了让你能跟上发生了什么,'[Background](anim_basics.html#background)' 帧是前一帧的精确复制,对所显示的内容不做任何改变。然而,它定义了在叠加下一帧图像之前需要清除的动画区域。接下来的 '[None](anim_basics.html#none)' 帧填入需要更改的像素,以及前一帧处置所清除的像素。在上面的动画中,这意味着塑造新孔洞所需的像素,以及用于填补前一个 '孔洞' 的像素。结果更小了,但因为添加额外帧本身也有代价,所以没有小很多。至少每个添加的帧也不带有各自的颜色表,否则由于额外颜色表的尺寸,这个动画实际上会变得更大!

Layer Optimize Plus - 自动帧二重化优化

我很高兴地说,从 6.2.7 版起,IM 现在可以作为其常规帧优化处理的一部分,自动进行帧二重化优化。不过,由于 '添加帧以使动画变小' 这一做法过于激进,它被赋予了自己独立的 "[-layers](https://imagemagick.org/command-line-options/#layers)" 方法 '**OptimizePlus**'。例如,让我们请 IM 来做帧二重化优化…… | |

  magick moving_hole.gif  -layers OptimizePlus   moving_hole_oplus.gif
  gif_anim_montage x2 moving_hole_oplus.gif moving_hole_oplus_frames.gif

[IM Output]
[IM Output]
也就是说,IM 给了你与前面帧二重化示例相同的结果。因此 GIF 文件仍是 [IM Text] 字节大小。不过 'OptimizePlus' 只有在结果动画(3 帧)中当前帧和下一帧的像素数减少时才会做帧二重化,所以我们可以让 IM 自行决定是否进行帧二重化。由于 "[-layers](https://imagemagick.org/command-line-options/#layers)" 方法 '[OptimizePlus](#optimizeplus)' 在创建帧优化的 GIF 动画时会添加额外的帧,它也会移除任何对最终动画没有改变的多余帧(并酌情合并延迟时间)。也就是说,它还会自动执行一次 '[RemoveDups](#removedups)'(见下文)。而 '[OptimizeFrame](#optframe)' 方法不会这样做。

移除重复帧 - 合并连续的重复图像

遗憾的是,如果你对这个动画执行合并,你也会得到上面所添加的全部额外帧。

  magick moving_hole_oplus.gif -coalesce gif:- |\
     gif_anim_montage x2 - moving_hole_oplus_cframes.gif

[IM Output]

为了让你能从合并后的动画中移除这些无用的重复帧,我们提供了 '**RemoveDups**' 方法。它将动画中的每一帧与下一帧比较,如果二者相同(颜色相似度由当前的模糊系数设定),就移除前一帧。此外,为了确保动画中的任何时序不会丢失,两帧的时序延迟也会被合并。例如……

  magick moving_hole_oplus.gif -coalesce -layers RemoveDups  gif:- |\
     gif_anim_montage - moving_hole_oplus_rmdups_frames.gif

[IM Output]

我们现在就得回了动画原本的合并形式。关于移除额外帧的另一种方法,请参见下面的 '[RemoveZero](#removezero)' 方法。

拆分帧更新 - 分别更新两处相距较远的变化

正如你在帧二重化中所见,通过把 '像素的清除' 与新像素的叠加分离,我们可以减小单个帧叠加的整体尺寸。然而这个动画仍会产生一些非常大的叠加,它们大多由实际上并未从一帧变到下一帧的像素组成。也就是说,主叠加帧只更新了两块相距颇远的相当小的区域,却因此产生了一张巨大的叠加图像。与其试图同时更新这两处变化、还把中间那些未变化的像素也全部包含进来,我们不如分别更新每一处区域。也就是说,我们 把帧更新拆分为两个阶段,每一处相隔的变化区域各占一个阶段。在这个例子里,我们可以先填补孔洞,再作为一次单独的更新去创建新的孔洞。这两处独立变化以何种顺序发生其实并不重要(除了可能与处置有关),但你应当尽量合乎逻辑。也可能其中一处变化比另一处更容易创建。例如,这里我插入额外的帧,把填补旧孔洞作为一次单独的更新,与 '挖出' 新孔洞区分开来。这既是更容易生成的中间帧,也是最合乎逻辑的动作顺序。当然,最后一帧不需要这么做,因为那一帧在动画循环之前就被丢弃了。 | |

  magick moving_hole.gif \
          \( +antialias -size 100x100 -delay 0 xc:SkyBlue \
             -fill DodgerBlue -draw 'circle 50,50 15,25' \
             -fill RoyalBlue  -draw 'circle 50,50 30,25' \) \
          \( +clone \) -insert 1 \( +clone \) -insert 3  +swap \
          -set dispose background  moving_hole_split.gif
  gif_anim_montage x2 moving_hole_split.gif moving_hole_split_frames.gif

[IM Output]
[IM Output]
请记住,添加的中间帧不同于其周围向用户显示的帧(那些具有非零时间延迟的帧)。这不是简单的 '帧二重化',而是分离两处相距较远的小片变化。这种添加中间帧的做法不是一个可以自动化的简单步骤。虽然有可能开发出一种聪明的启发式算法来生成这些中间帧,但应当怎么做并不总是显而易见,更不用说是否应当去做。如果你想尝试设计这样一种启发式算法,请给我发邮件。 那么在添加这些额外帧之后,我们就试一下标准的帧优化…… | |

  magick moving_hole_split.gif \
               -layers OptimizeFrame     moving_hole_split_opt.gif
  gif_anim_montage x2 moving_hole_split_opt.gif \
                      moving_hole_split_opt_frames.gif

[IM Output]
[IM Output]
这些 '零延迟中间帧' 的添加,使得这个动画比原来未优化的动画能更好地做帧优化,生成了一个 [IM Text] 字节的动画。然而对这个特定案例而言,它不如使用自动化的帧二重化技术(见上面的 'OptimizePlus' layers 方法)。不过,添加 '零延迟中间帧' 并不妨碍你同时也运用那种 '帧二重化' 技术…… | |

  magick moving_hole_split.gif \
               -layers OptimizePlus moving_hole_split_oplus.gif
  gif_anim_montage x2 moving_hole_split_oplus.gif \
                      moving_hole_split_oplus_frames.gif

[IM Output]
[IM Output]
这个动画现在每次帧更新都有两个额外的 '零延迟中间帧'。第一个填补旧孔洞,第二个清除一块将包含透明像素的区域,最后再恢复那些本不该被清除的像素。结果是对这个特定问题动画可能达到的最优帧优化,最终文件大小为 [IM Text] 字节。也就是说,我们的 4 帧动画通过添加 6 个零时间延迟帧反而变小了!比原来的帧数多了一倍还多。奇怪但确实如此!当然,如果 GIF 动画程序真能把零延迟中间帧识别为它们的本来面目——即动画真实各帧之间的中间更新——那就更好了。不过即便如此,当这些更新彼此高度分离且非常小时,额外帧所造成的轻微停顿也很少能被看出来。
当然,如果动画中两处相隔的部分实际上并不相关,那它们就不需要在时间上同步。另一种替代方案是:不添加额外帧,而是把动画拆分为两个完全独立的动画,让它们在网页上一起显示。参见拆分动画。不过这个特定动画无法被拆分为在时间上互不相关的独立动画。首先,相隔的变化需要在时间上同步;其次,那四块会变化的区域在水平和垂直两个方向上都彼此重叠。这意味着一个简单的 HTML '表格' 无法把这些子动画重新拼合为一个完整的整体,除非借助某种 CSS 花招。你能证明我错了吗? 未来:引用一个更好的 '为两个相距较远的对象制作动画' 的示例,放在 '动画处理' 中,比如涉及两个各自独立移动的对象。

移除零延迟帧 - 移除中间更新

当然,有时你并不关心、或者想从动画中移除这些添加的中间帧,只保留那些会实际向用户显示一段时间的帧。你不能只是合并动画后再用 '[RemoveDups](#removedups)' 方法,因为并非所有 '中间帧' 都与其周围的帧相似,因而它们不是重复帧。不过,由于这类帧具有零时间延迟,你可以使用另一个特殊的 "[-layers](https://imagemagick.org/command-line-options/#layers)" 方法 '**RemoveZero**',它会移除任何时间延迟为零的帧。同一个方法也会移除用帧二重化和 '[OptimizePlus](#optimizeplus)' 技术添加的帧。例如……

  magick moving_hole_split_oplus.gif -coalesce -layers RemoveZero gif:- |\
     gif_anim_montage - moving_hole_split_rmzero_frames.gif

[IM Output]

这同样把动画还原为仅含用户可见的帧,从而简化了动画。当然,在移除零延迟中间帧之后,要再把它们加回去就非常困难了,因为其中所含的变化信息已经丢失。因此,动画之后可能就不太能做好帧优化了。毕竟优化正是这类帧的主要用途之一。

帧优化的结果与总结

我们来总结一下对移动孔洞动画所做的优化……

[IM Text]

如你所见,借助一些复杂的帧处理,加上 IM 的帮助和一些人工干预,我们能够把 '移动孔洞' 动画做帧优化到几乎只有原大小的一半,尽管帧数变成了原来的近三倍。当然,结果会因动画不同而差异很大,但我们用于帧优化的技术是相同的。它只需要一点点细心与预先思考,而这正是人类擅长、计算机不擅长的。 | _问题在于,IM 在决定如何做帧优化时,不应只考虑当前所看的这组帧中的像素数,还应考虑所添加的额外帧的整体大小,也许还有所获得的整体压缩结果。

另一方面,IM 也没有考虑除了直接涉及的帧之外,可能带来的像素数节省。也就是说,由于帧二重化或所用的处置方法,后面的帧尺寸也可能更小。当抉择是否使用 'previous image dispose' 方法时尤其如此,因为它可能在动画序列的后段带来可观的像素数缩减,而不是立即在紧接的下一帧。这里的良好抉择往往需要人工输入。

因此,我无法保证 IM 会为某个特定动画产生最佳的优化抉择。然而它确实做了很好的尝试,而且不使用递归就做出这一抉择。也就是说,它只用即时的像素数来做决定。

一种递归算法——先做出一个抉择,再看该抉择(连同沿途更远的递归抉择)所导致的动画最终大小——可以产生有保证的最佳优化。然而它也可能是一个极其缓慢的操作符,对于一个大动画可能要花上数年才能做出最终决定。它还需要把压缩优化的抉择也纳入其中,因为这些会影响最终结果。换言之,虽然这样的算法能保证最佳优化,但它是以沉重的计算成本换来的。

当然,一个对动画想要达成的效果有深入了解的人,在复杂动画上通常会做得更好,正如你在上面拆分帧更新中所见。

如果你想尝试创建一个递归的 GIF 优化操作符,请务必去做。我会尽我所能提供帮助。它会胜过市面上几乎所有其他 GIF 优化程序。而且大多数 GIF 动画开发者,多半也会对你的努力(在金钱方面)非常感激。_
---|---


半透明处理

GIF 文件格式不允许使用半透明像素(参见 GIF 布尔透明度)。这是事实,在你能正确优化动画、甚至只是把它保存为 GIF 格式之前,你都需要以适合该动画的方式处理可能存在的任何半透明像素。默认情况下,如果你不处理这些像素,IM 会使用 50% 阈值把这些像素变成完全透明或完全不透明。然而那也许并不是处理该问题的最佳方式,尤其是对于包含大片半透明像素的图像,比如阴影效果。例如,我想创建一个星际之门中阿斯加德传送的动画,它可以把几乎任意子图像作为被传送的对象。

  magick -channel RGBA -fill white \
          \( medical.gif -repage 100x100+34+65 -coalesce -set delay 200 \) \
          \( +clone -motion-blur 0x20+90 -blur 0x3 -colorize 100% \
                +clone -colorize 30%  +swap  -composite  -set delay 10  \) \
          \( +clone -roll +0-20 -blur 0x3 -colorize 30% \
             -motion-blur 0x15+90 -motion-blur 0x15-90 -set delay 10 \) \
          \( +clone -colorize 30% \
             -motion-blur 0x30+90 -blur 0x5 -crop +0+10\! \) \
          \( +clone -motion-blur 0x50+90 -blur 0x2 -crop +0+20\! \) \
          \( +page -size 100x100 xc:none -set delay 200 \) \
          -set dispose background -coalesce   -loop 0     teleport.miff
  gif_anim_montage teleport.miff teleport_frames.png

[IM Output]

我特意把动画保留为 IM 内部的 MIFF: 文件格式,因为这能确保原始图像不被修改地保存下来,并使用 PNG: 文件格式来显示各帧,以便你能看到其中包含的所有半透明像素!这不仅对含半透明像素的动画重要,对含大量颜色的动画也很重要。一旦图像序列被保存为 GIF,你生成良好颜色优化的机会就会从大好变为困难。 好了,我有了一个动画序列。如果我试图把它直接保存为 GIF,IM 就会对所有那些半透明像素做阈值处理。 |

  magick teleport.miff teleport_thres.gif
  gif_anim_montage teleport_thres.gif teleport_thres_frames.gif

[IM Output]

[IM Output]

结果看起来完全不像我们想要的样子。默认的 50% 透明度处理使动画看起来像一个逐渐缩小的 '鸡蛋'。绝对不是我想用这个动画达成的效果。如果这种透明度处理方式可以接受,那么在继续你其他优化之前,就该这样应用它……

  magick teleport.miff -channel A -threshold 50% +channel \
                 ...do further processing now...       teleport.gif

使用上面这种自己动手的做法还有一个额外的好处,就是你可以控制阈值水平。比如用 '10%' 去除几乎所有存在的半透明像素,或用 '90%' 让它们全部变得不透明。 |

  magick teleport.miff -channel A -threshold 90% +channel teleport_thres90.gif
  gif_anim_montage teleport_thres90.gif teleport_thres90_frames.gif

[IM Output]

[IM Output]

但对于像这样的动画,应用阈值并不是一个好的解决方案,因为它实在破坏了我想要达成的透明效果。 要保留上面这个动画中所有特效,最好的整体方案就是直接添加一个纯色背景。 |

  magick teleport.miff -bordercolor skyblue \
                  -coalesce -border 0 teleport_bgnd.gif
  gif_anim_montage teleport_bgnd.gif teleport_bgnd_frames.gif

[IM Output]

[IM Output]

这会去除动画中的所有透明度,代价是只允许动画在特定背景色上工作。但如果你是在为某个特定网页创建动画,那也许完全可以接受。不过请注意,对于有清晰轮廓的图像,使用这样的抖动图案会使锋利的边缘产生 '点状' 轮廓。因此在一般情况下并不推荐。另一种解决方案是尝试生成某种透明与不透明像素的图案,以尽量保留图像的半透明。为此 IM 提供了一大批能解决该问题的抖动选项。未来:链接到一个将要创建的关于透明度抖动的章节,比如量化与抖动 请注意,看似显而易见的第一个方案——对 alpha 通道使用单色抖动——并不简单,可能需要一些高级的多图合成才能正确完成。 一个简单的方案是使用扩散像素有序抖动技术,它可以被限定为只作用于 alpha 通道,以去除半透明像素。 |

  magick teleport.miff -channel A -ordered-dither o8x8  teleport_od.gif
  gif_anim_montage teleport_od.gif teleport_od_frames.gif

[IM Output]

[IM Output]

结果还算合理,但看起来更像一个正在溶解的对象,而不是在传送。 使用半调会通过让透明图案更加醒目而产生好得多的效果。 |

  magick teleport.miff -channel A -ordered-dither h8x8a teleport_htone.gif
  gif_anim_montage teleport_htone.gif teleport_htone_frames.gif

[IM Output]

[IM Output]

不过对这个特定动画,我发现使用用户自定义抖动映射来生成竖线(由一个水平线抖动图案而来),能产生一种在去除半透明像素的同时增强传送动画的效果。 |

  magick teleport.miff -rotate 90 \
          -channel A -ordered-dither hlines -rotate -90 teleport_lines.gif
  gif_anim_montage teleport_lines.gif teleport_lines_frames.gif

[IM Output]

[IM Output]

如你所见,处理 GIF 动画中的半透明有相当多的可能做法。


颜色优化

处理半透明像素只是 GIF 文件格式的第一个限制。下一个是动画中每张颜色表 256 色的上限。你可以为每一帧使用一张单独的颜色表。这意味着单个动画可以拥有超过 256 种颜色。然而,即便如此也未必总是个好主意。如果你只想快速了解一下可用的颜色优化选项,我建议你跳到视频转 GIF 转换的示例,那里动画的颜色问题最为严重。

GIF 颜色问题

GIF 动画在处理颜色上尤其棘手:首先它不允许半透明颜色,其次每帧有 256 色上限、或 256 色的全局上限。最后,除非某帧中某个像素所用的颜色,在下一帧该部分图像 '没有' 变化时也与之相同,否则你最好的帧优化也不会奏效!这看似是个简单的问题,但减色本身就是一个极其复杂的领域,在 IM 示例中需要用一整个章节来讲。颜色问题其实正是你在万维网上找到的大多数 GIF 动画要么是卡通类、要么很难看的原因。尤其是如果由某个更大版本的动画缩放而来。在缩放动画中,颜色优化所需的功夫,很可能比实际的缩放过程本身还要多。这里我假设你有动画的原始来源。但那并不总是可能的,所以如果你在优化一个被改动过的 GIF 动画,可能需要额外的谨慎。不过,如果你有一个颜色过多的动画,首先需要记住的是……

不要直接保存为 GIF 格式,
请使用 MIFF 文件格式,或者单独的 PNG 图像。

一旦你保存为 GIF,你就失去了对 GIF 颜色优化努力的控制,而且你多半会得到一个非常难看、且用各种帧优化技术都很难优化的 GIF 动画。

Speed 动画 - 一个颜色过多的动画

首先我们需要生成一个颜色数量极其庞大的 GIF 动画,这样才能真正测试颜色优化中涉及的问题。 | |

  magick -dispose none -channel RGBA \
          \( medical.gif -repage 100x60+5+14  -coalesce -set delay 100 \) \
          \( medical.gif -repage 100x44+34+6  -coalesce -set delay 10 \
             -motion-blur 0x12+0  -motion-blur 0x12+180 -wave -8x200 \) \
          \( medical.gif -repage 100x60+63+14 -coalesce -set delay 100 \) \
          \( medical.gif -repage 100x44+34+6  -coalesce -set delay 10 \
             -motion-blur 0x12+0  -motion-blur 0x12+180 -wave +8x200 \) \
          null: \( +page  -size 120x15 xc:SkyBlue xc:RoyalBlue \
                   -size 120x70 gradient:SkyBlue-RoyalBlue \
                   +swap -append -blur 0x3 -background white -rotate -25 \
                \) -gravity center -compose DstOver -layers Composite \
          -loop 0   speed.miff

  magick  speed.miff  speed.gif
  gif_anim_montage  speed.gif  speed_frames.gif

[IM Output]
[IM Output]
请注意,我没有把动画直接保存为 GIF 格式,而是先把它保存为一个 MIFF 格式文件 "[speed.miff](../static/img/anim_opt/speed.miff)"。这保留了原本创建(或修改)的动画的所有方面,包括 GIF 元数据、时序延迟,以及图像的所有颜色而不失真。只有在保留了原始动画之后,我才把它直接转换为 GIF 格式。这样我就能展示上面的代码想要达成什么,以及我为什么把它叫作 'speed'。这么做也是为了提供一个基准 GIF 动画,供研究和之后比较。那么我们来看看原始动画的各项细节……

  magick identify -format "Number of Frames: %n\n" speed.miff | head -1

[IM Text]

  magick identify -format "Colors in Frame %p: %k\n"  speed.miff

[IM Text]

  magick speed.miff +append  -format "Total Number of Colors: %k"  info:

[IM Text]

如你所见,动画中的每一幅图像都有非常大量的颜色。不仅每一帧的颜色数量各不相同,而且第一帧和第三帧在颜色上非常相似,尽管并不完全一样。然而 GIF 文件格式每帧最多只能保存 256 种颜色,ImageMagick 把它保存为 GIF 格式时,是以最快、最笨的方式完成的……它减少了动画中每一帧的颜色数量(这个过程称为颜色量化)……

  magick identify -format "Colors in Frame %p: %k\n"  speed.gif
  magick speed.gif +append  -format "Total Number of Colors: %k"  info:

[IM Text]

因为每一帧减少后的颜色数量略有不同,IM 还需要为动画中的每一帧提供一张单独的颜色映射表。这意味着该 GIF 文件有一张 '全局颜色表'(它总是有的),但还有三张单独的 '局部颜色表'。"magick identify" 命令无法告诉你一个 GIF 文件有多少张这样的局部颜色表,因为这类信息过于格式相关,对 IM 通常所做的图像处理并不重要。不过更专门的 "[Giftrans](http://www.ict.griffith.edu.au/anthony/software/#giftrans)" 程序能告诉你使用了多少张底层的局部颜色表……

  giftrans -L speed.gif 2>&1 | grep -c "Local Color Table:"

[IM Text]

如你所见,这个动画有 [IM Text] 张局部颜色表,比图像中存在的帧数少一张,正如我所预料的。不仅每一帧有一组不同的颜色,而且颜色的图案(图像抖动图案)也略有不同,如误差校正抖动的问题中所述。通常 IM 颜色量化与抖动的这一默认操作非常好,非常适合图片,尤其是真实生活照片。事实上,动画的各个单帧一般看起来都很棒。所有问题都出现在我们随后试图把那些各自减色后的帧串成单个动画序列的时候。

先做帧优化还是先做颜色优化?

正如你在上面所见,把动画直接保存为 GIF 格式是可行的,但你会得到相当多的逐帧颜色差异,这对之后的帧优化不利(你稍后会看到)。为防止颜色差异造成这类问题,你可以在保存动画之前先做帧优化,从而避免引入逐帧的颜色差异。 但要提醒的是,在减色之前做帧优化会改变减色的动态。往往优化后的子帧中出现的静止不动区域会更少,这意味着该帧的颜色量化可以给那些颜色赋予更低的重要性,因而更少的颜色。

模糊颜色优化

然而有时你无法接触到动画被保存为 GIF 格式之前的原始版本。如果你是从万维网下载了原始动画,尤其如此。这意味着你手上已经是一个带有所有那些 GIF 颜色失真的动画,从而给之后的优化带来问题。现在,由于逐帧使用了略有不同的一组颜色,且动画中每一帧使用了不同的像素图案,每一帧都可以被视为一幅完全不同的图像。例如,我们来比较第一帧和第三帧,它们共享大量相同的背景图像…… |

  magick compare  speed.gif'[0,2]' speed_compare.gif

[IM Output]
上面例子中的红色区域显示了两块存在差异的实心方形区域,正如你所预期的。但它也显示出勾勒着两帧背景轮廓的一条条颜色差异带。这些代表沿背景渐变边缘的 '翻搅' 抖动图案,那里用了不同颜色的像素来表现完全相同的背景。而这一对帧还是背景受不同颜色集和抖动图案干扰最小的一对。连续帧之间的实际差异要糟糕得多,会产生近乎一整片的红色差异。 | _如果你的源图像是用 JPEG 图像格式存储的,那么像这样的图像差异也是个问题。这种格式使用一种有损压缩方法,即便在 100% 质量下也会导致图像中出现轻微的颜色差异。不过这些差异通常局限在实际差异区域周围的光晕上,而不是遍布整幅图像。

我只能说,除非你打算用一张单独的图像作为所有帧的静态背景图像,否则要避免在动画中使用 JPEG 图像。_
---|---
由于动画中如此多的像素逐帧都不相同,那么当我们试图对动画做帧优化时得不到任何优化,也就不足为奇了……

  magick speed.gif  -layers OptimizeFrame  speed_opt2.gif
  gif_anim_montage  speed_opt2.gif  speed_opt2_frames.gif

[IM Output]

然而,动画各帧未变化部分之间的大多数像素颜色差异其实相当小。如果不是这样,那也就算不上很好的减色了。这意味着,通过请 IM 稍微放宽其颜色比较,你可以让它忽略微小的颜色差异。这是通过设置一个合适的模糊系数来完成的。

  magick speed.gif  -fuzz 5%  -layers OptimizeFrame  speed_opt3.gif
  gif_anim_montage speed_opt3.gif speed_opt3_frames.gif

[IM Output]

如你所见,加上一个小的模糊系数后,IM 现在会忽略那些仅略有不同的像素,生成一个还算不错的帧优化。你需要多大的模糊系数,取决于 IM 在对原始图像减色时遇到了多大的麻烦。在这个例子里麻烦不大,所以只需要一个非常小的系数。如果一个小的模糊系数能产生可接受的结果,那就把它设置好用于你的帧优化透明度优化。只是要记住,你仍有每一帧一张单独的颜色表需要处理,这就是接下来要讨论的一点。还请注意,帧优化决定对第二帧使用 'Previous 处置'。也就是说,显示完第二帧后,在叠加之前把图像恢复到上一帧处置(第一幅图像)。这比全程不使用处置得到了更小的叠加图像尺寸。如果你只想要一个简单的、全程只使用 None 处置叠加动画,你本可以改用旧的 Deconstruct 操作符(也称为 Layers CompareAny)来生成它。

  magick speed.gif  -fuzz 5%  -deconstruct  speed_opt4.gif
  gif_anim_montage speed_opt4.gif speed_opt4_frames.gif

[IM Output]

生成单一全局颜色表

现在,由于每一帧都有一组不同的颜色,IM 被迫为每一帧保存一张单独的颜色表:第一帧一张全局的,后面各帧 3 张局部颜色表。例如,这里我用非常简单的 "[Giftrans](http://www.ict.griffith.edu.au/anthony/software/#giftrans)" 程序来报告创建了多少张帧颜色表。

  giftrans -L speed.gif 2>&1 | grep -c "Local Color Table:"

[IM Text]

对于一个完全合并的(或胶片条般的)动画,每帧拥有单独的颜色表是完全没问题、合情合理的,在这种情况下这不是问题。也就是说,对于图像差异很大的幻灯片放映,单独的颜色表会产生最好看的结果。因此这是 IM 的正常工作行为。不过所有这些额外的颜色表代价很高,因为每张颜色表都可能占用很多空间。图像中每一帧最多可达 768 字节(256 色 × 每色 3 字节,即 3/4 千字节)。不仅如此,GIF 压缩不会压缩这些颜色表,只压缩像素数据!如果用这么多文件空间来存放单独的颜色表成了问题,尤其对于一幅颜色变化不大的图像(大多数 GIF 动画都是如此),那你可以让 IM 只使用所需的全局颜色表,而不添加任何局部颜色表。---要移除局部颜色映射,所有图像都必须变为调色板类型并全部使用同一张调色板。在命令行上,你可以通过设置 "-map image" 来定义共用调色板,你不能使用 -colors,因为它作用于单个图像。命令行的解决方案是一个特殊的 "[+map](https://imagemagick.org/command-line-options/#map)" 选项,它做一次全局减色到一张共用调色板,并把该调色板添加到所有图像上。注意:对图像的任何改动都很可能使调色板失效,所以虽然减色应当在你做 GIF 帧优化和/或压缩优化之前完成,共用调色板却需要放在最后、就在保存之前。如果 "[+map](https://imagemagick.org/command-line-options/#map)" 不需要减少图像中的颜色数量,它就不会去做,也不会抖动颜色,只是给所有图像添加一张共用调色板。--- 如果所有帧都使用同一张颜色调色板,IM 就能生成单一的全局颜色表。在 IM 中,颜色调色板只有两种途径被赋予某幅图像:一是从使用这样一张调色板的图像格式读入,二是用 "[-map](https://imagemagick.org/command-line-options/#map)" 减色操作符为其指定一张。详见用预定义颜色映射表抖动。生成这张单一颜色表的一种方法,是简单地把所有帧 "[-append](https://imagemagick.org/command-line-options/#append)" 拼在一起,然后用 "[-colors](https://imagemagick.org/command-line-options/#colors)" 命令把颜色数量减少到一个最小集合(少于 256,如果你想要更小的颜色表还可以更少)。得到的颜色表随后可以用 "[-map](https://imagemagick.org/command-line-options/#map)" 应用到原始图像上。例如,这里我把图像减到一组 64 色。这用到了特殊的 MPR 内存寄存器,把生成的颜色映射赋给 "[-map](https://imagemagick.org/command-line-options/#map)" 命令。 |

  magick speed.gif \
          \( -clone 0--1 -background none +append \
              -quantize transparent  -colors 63  -unique-colors \
             -write mpr:cmap    +delete \) \
          -map mpr:cmap      speed_cmap.gif

[IM Output]
现在,如果你用 "[Giftrans](http://www.ict.griffith.edu.au/anthony/software/#giftrans)" 检查得到的动画,你会发现该图像现在使用一张单一的 '全局' 颜色表,而不是每帧一张单独的颜色表。 | _在把图像拼在一起之前,我使用了 'None' 的 "[-background](https://imagemagick.org/command-line-options/#background)" 颜色,这让你可以对未合并的动画使用它,而不会添加额外不需要的颜色。

使用了 'transparent' 色彩空间这一特殊的 "[-quantize](https://imagemagick.org/command-line-options/#quantize)" 设置,以确保 IM 不会在其颜色映射中尝试生成半透明颜色。这么做没有意义,因为我们要把结果保存为无法处理半透明的 GIF。

最后,我把颜色减到 63 色,为一个透明色留出空间。有些动画需要透明度,而另一些(比如这个)之后可能还会在压缩优化中需要它。_
---|---
为了让这更简便,IM 还提供了一个特殊选项 "[+map](https://imagemagick.org/command-line-options/#map)",它会在所有帧上生成一张共用颜色映射(256 色),并全局应用。这比上面自己动手的方法简单得多。 |

  magick speed.miff  -alpha off +map   speed_map.gif

[IM Output]
这在得到的图像中产生了 [IM Text] 张 '局部'(或多余不想要的)颜色表。我会在接下来的优化章节中使用单一颜色表版本的动画,不过你其实可以在动画优化的任何时点这么做,尤其是在最终保存之前。作为颜色表优化的结果,之前直接转换得到的 GIF 为 [IM Text] 字节的动画,在使用 "[+map](https://imagemagick.org/command-line-options/#map)" 操作符后现在是 [IM Text] 字节。一个动画拥有的帧(和 '局部颜色表')越多,节省就越大。现在,由于对动画的任何修改通常都会移除为各图像保存的调色板,所以让 "[+map](https://imagemagick.org/command-line-options/#map)" 操作符成为把动画保存为 GIF 之前的最后一个操作,就很重要。请记住

移除局部颜色映射应当是保存为 GIF 格式之前的最后一次优化。

有序抖动,去除 '静电噪声'

建设中

不过请注意,在我们目前所看的所有技术中,都可能存在一种逐次叠加而变化的抖动图案。一种像电视静电噪声那样翻搅的像素。

... small number of colors ...

在对一块较小的不动区域做帧优化时,你甚至可能得到矩形区域的静电噪声,看起来更糟。……有序抖动……目前请参阅更实用、细节较少的视频转 GIF、优化总结


压缩优化

一旦你通过处理半透明像素、并运用颜色与帧优化,把动画保存为 GIF 格式,你还可以通过迎合 GIF 压缩算法来获得一些更小的文件尺寸缩减。GIF 文件格式可用的 LZW 压缩或行程长度压缩,在遇到更大片的恒定颜色区域、或反复重复的像素序列时会压缩得更好。

透明度优化

正如你在帧优化中所见,一张叠加的图像往往只是在重复已经显示出来的东西。也就是说,它叠加的是在 GIF 处置方法应用之后就已经存在的、同样颜色的像素。可是何必去重复那些像素呢。如果你在图像中已经使用了透明度,你就有一个可用的透明像素色。把那些区域转换成透明,就能得到更大片的均匀透明像素。相比使用为匹配同一被叠加区域所需的各种不同颜色的混合,这样能压缩得更好。例如,这里有一个简单的帧优化后的叠加动画…… [IM Output] [IM Output]
现在我们用 "[-layers](https://imagemagick.org/command-line-options/#layers)" 方法 '**OptimizeTransparency**'(IM v6.3.4-4 添加)来把任何不改变显示结果的像素替换为透明。
  magick bunny_bgnd.gif -layers OptimizeTransparency \
                                    +map   bunny_bgnd_opttrans.gif
  gif_anim_montage bunny_bgnd_opttrans.gif bunny_bgnd_opttrans_frames.gif

[IM Output]
[IM Output]
如你所见,子帧现在有了大片透明区域,它们不影响最终得到的动画。需要更改像素的区域仍被叠加,但不变化的区域已被变成透明。这也包括被制作成动画的对象内部,从而留下相当难看的 '孔洞'。由于更大片的恒定透明颜色区域(理论上)会压缩得更好,得到的这个 '乱糟糟' 的动画就小了很多,把文件大小从帧优化结果的 [IM Text] 字节减到了 [IM Text] 字节。这是以极小的努力换来的相当大的节省。请注意,该优化方法不需要是一个合并后的动画,而且子帧的尺寸保持不变,以保留本帧及后续帧的处置需要。因此,任何节省都仅仅体现为对动画中相同像素数的压缩率改善,而不是保存到文件中的实际像素数的节省。因此它应当在你完成所有所需的帧优化之后,作为你最后的优化步骤之一来做。

FUTURE: link to a 'remove background' from animation

当然,和大多数其他 "[-layers](https://imagemagick.org/command-line-options/#layers)" 方法(比较或优化)一样,你可以指定一个模糊系数来调整颜色被认为 '有多相似'。这让你能处理那些颜色抖动很糟的动画,不过如果你研究过上面的颜色优化,就不该有这个问题。免费的 GIF 动画工具 "**[InterGIF](http://utter.chaos.org.uk/~pdh/software/intergif.htm)**" 也提供了上面所示的这种透明度压缩优化,但没有同时支持 '模糊系数' 来让 '相近' 颜色的变化也变透明的能力。我不推荐它,除非作为 IM 不可用时的一个替代。

LZW 优化 - (非 IM)

有些应用程序可以进一步优化动画中图像的压缩率,使它们更小。然而要做到这一点,需要关于 GIF 图像文件格式通常所用的 LZW 压缩的专门知识。基本上,如果某个特定的像素序列已经被 LZW 压缩算法处理过,它就不会费事把它们变成透明像素,因为这么做不会改善图像的压缩。这听起来很奇怪,但确实奏效。遗憾的是 ImageMagick 不会这样做,因为这是一个极其复杂的过程,需要大量技巧和资源才能得到一个在一般情况下产生较好结果的合理启发式算法。不过我可以用 "[Gifsicle](http://www.lcdf.org/gifsicle/)" 应用程序在其最高 '-O2' 优化级别下,给你一个这种技术的实际例子。 |

  gifsicle -O2 bunny_bgnd.gif -o bunny_bgnd_lzw_gifsicle.gif
  gif_anim_montage bunny_bgnd_lzw_gifsicle.gif bunny_bgnd_lzw_frames.gif

[IM Output]

[IM Output]

LZW 压缩优化把图像从简单透明度优化的 [IM Text] 字节,用 "Gifsicle" 减到了 [IM Text] 字节。改善不大。但更重要的一点是,虽然 LZW 优化把不变的像素转成了透明(正如我们上面用透明度优化所做的),它却没有改动已经出现过的像素序列。也就是说,只有那些在动画中 尚未 被重复过的像素组被改动了,因为那些像素(据推测)已经能用 LZW 压缩图案压缩得很好了。请注意,为了生成重复的像素图案而选择应把哪些像素变透明,是非常复杂和困难的,甚至可能取决于具体的 LZW 实现。它是一种启发式方法,而不是一个完全可预测的算法。因此,不同的程序对不同的具体图像通常会产生不同的结果。一个程序可能对某一幅图像产生更好的压缩率,而另一个程序可能对另一幅不同的图像更好。

有损 LZW 优化 - (非 IM)

另一种压缩改善方法涉及对像素颜色本身做轻微修改,改成 '相近的颜色匹配',以增加图像中颜色引用的重复度。重复的图案自然压缩得更好,因而能产生更高的压缩率。前述 "Gifsicle" 应用程序的一个分支,名为 giflossy,也生成一个 'gifsicle' 程序,但带有以细微方式修改图像(它是 '有损' 的)以进一步大幅缩小 GIF 图像(尤其是动画)尺寸的选项。 | |

  gifsicle -O3 --lossy=80 bunny_bgnd.gif -o bunny_bgnd_giflossy.gif
  gif_anim_montage bunny_bgnd_giflossy.gif bunny_bgnd_lossy_frames.gif

[IM Output]

[IM Output]


Created: 22 March 2007 ((sub-division of "animation")
Updated: 23 April 2007
Author: Anthony Thyssen, Anthony.Thyssen@gmail.com
Examples Generated with: [version image]
URL: https://usage.imagemagick.org/anim_opt/

这产生了 [IM Text] 的大小,惊人地缩减了 1/3。遗憾的是这种方法涉及改动得到的图像,因此该优化是有损的,因为它会丢失细微的颜色信息。好处是它允许你压缩单个帧,而不是逐帧之间的优化。例如,这里我生成了一张前面非有损 LZW 压缩与有损 LZW 压缩之间的差异图像。 |

  magick compare bunny_bgnd_lossy_frames.gif bunny_bgnd_lzw_frames.gif \
          bunny_bgnd_diff_frames.gif

[IM Output]

如你所见,几乎所有颜色修改都发生在原来的 '草地' 背景图像上,而不是卡通兔子上。基本上,它只是把东西改动了刚好足以造成巨大差别的程度,而对这幅图像来说,观感上并没有真正明显的差异。当然,颜色量化与抖动本身就是一种有损操作,而且通常本来就需要,所以对 GIF 图像和 GIF 动画使用有损压缩方法,并不被认为很糟。 另一个这类算法的例子是被 Photoshop 申请专利使用的,参见 美国专利 7031540 - 以最小视觉失真提升图像的 Lempel-Ziv 可压缩性的变换。它读起来很费劲,但详细说明了它为实现更好压缩所用的方法。

有序抖动的 LZW 优化

由于抖动过程通常是比 LZW 优化更有损的过程,一个更好的方案也许是尝试把可重复的图案作为抖动过程的一部分引入。这可以通过使用有序抖动来生成这样的图案,从而带来比前面所有 LZW 优化方法都强得多的 LZW 压缩节省。作为额外好处,它还能改善带静态背景的真实生活动画的帧优化。如果你人为地清理背景使其真正变为一个静止不变的背景,那就尤其如此。当然,有序抖动压缩优化只对尚未被抖动或以其他方式颜色优化过的图像有效。因此它只对尚未为 GIF 图像格式优化过的动画有效。此外,目前 IM 的有序抖动只对均匀颜色调色板有效。IM 尚未实现有序抖动的 '最佳颜色' 或 '用户提供' 调色板,尽管我见过有程序对非常有限(且固定)的颜色调色板使用这样的算法。你知道这样的算法吗? 关于使用有序抖动改善 LZW 压缩优化的实际示例,请参见有序抖动视频

其他 LZW 优化

其他 LZW 优化的改善,也可以通过对图像中 '抖动图案' 的其他重新排列来实现。有些 GIF 工具正能做到这一点。不过任何这类优化都应当在获批之前先经人眼检查,因为有时可能产生细微但糟糕的颜色变化。

压缩优化总结

这里是使用压缩优化所取得的最终文件尺寸的完整总结。

[IM Text]

如你所见,相比内置的透明度优化,使用非常复杂的 LZW 优化只带来了最终动画尺寸的轻微改善。然而结果在众多可用的 GIF 优化应用程序之间、以及被优化的具体动画之间,也高度多变。如果你真的需要压榨出文件尺寸的最后一个字节,那么 LZW 优化也许正是你所需要的。而如果你真的需要最好的结果,你应当尝试若干不同的程序(因而是不同的启发式实现),看看哪一个对你的具体动画压缩得更好,包括它们还提供了哪些其他优化功能。通常透明度优化对大多数用途已经足够好了。LZW 优化只会产生略好的结果,为网络传输尺寸带来非常微小的节省,而不是磁盘存储尺寸,因为后者使用更大的存储 '块'。因此,我觉得 LZW 优化有些过度,我认为不值得那份努力,也不值得那笔钱(这些工具大多是商业出售的)。 | 遗憾的是,我发现这些 GIF 优化器并不能很好地处理 _所有 类型的预优化动画。

例如,我的测试表明 "[Gifsicle](http://www.lcdf.org/gifsicle/)" 无法处理一个已经用 'background disposal' 技术优化过的动画。

另一方面,我发现 "[InterGIF](http://www.chaos.org.uk/~pdh/software/intergif.htm)" 不能处理已经优化为使用初始画布和 'previous disposals' 技术的动画。它还局限于透明度优化的使用,而这一点 IM 现在已经提供。

因此我建议你不要通过把一个工具的输出喂给另一个工具,来混用 GIF 优化工具。至少不要在没有先合并动画以移除任何先前帧优化的情况下这么做。

IM、Gifsicle 和 InterGIF 都提供了这类合并选项以移除它们各自的优化,不过我无法保证非 IM 应用程序会正确合并 所有 动画。IM 会。

_
---|---
由于你无法把这些程序与 IM 先进的帧优化技术(它会自动选择并切换使用不同的 GIF 处置技术)可靠地配合使用,我常常发现 IM 产生的整体结果比单纯使用这些 LZW 压缩优化器更好。我还建议你之后再合并一次结果,并把其各帧与原始未优化动画比较,以确保非 IM 程序没有以某种方式把动画彻底搞坏(见上面的注)。相信我,我见过这种事发生,脚本应当二重检查动画是否保持有效。
另一篇关于此类优化的教程(使用 Windows 工具)是 WebReference 帧优化。请注意,该站点的命名有误,因为它讲的是压缩优化。


细微优化

还有几种可用于 GIF 动画的其他优化技术,它们往往因为太过显而易见而被忽略。

  • 移除 GIF 注释。
    许多 GIF 动画都添加了一大段文本注释。这些往往是图形编辑器作为一种广告形式自动添加的。例如,"Gimp" 默认会给图像添加 "Created with The GIMP"。如果注释并不需要,它就是在浪费空间。在保存 GIF 之前,通过给 IM 的 "magick" 命令添加一个 "[+set](https://imagemagick.org/command-line-options/#set) comment" 操作符来移除它们。不过请注意,如果注释是一段版权声明,出于法律原因,移除它也许不是个好主意。
  • 减少颜色数量。
    如果动画用更少的颜色看起来也还行,就使用一张更小的颜色表。颜色表总是 2 的幂,所以如果你能用少于 32 色,那就比用 256 色小得多。这一点尤为重要,因为颜色表不会被用于 GIF 图像数据的 LZW 压缩所压缩。此外,使用更少的颜色通常会产生更好的 LZW 压缩,因为能找到更多常见的像素序列。不过这并不总是如此,因为(减色导致的)颜色抖动也可能使压缩变差。关掉抖动或使用有序抖动在这里可能很重要。
  • 把用户可见的帧数减半。
    如果你能接受不那么流畅的动画,那么把总帧数减半可以对最终文件尺寸带来不错的改善。当然,你不会得到一半大小的文件,而且动画质量会降低。但它可以带来非常大的文件尺寸缩减。
  • 裁剪/缩放动画。
    更小的图像尺寸意味着更小的文件尺寸。所以如果你不需要一个大动画,就不要用大动画。在列表中,用一个小缩略图来代表一个更大的动画或视频,往往比真东西更可取。
  • 替代压缩。
    如果你不打算把动画当作动画使用,也就是你只想存储它,那就关掉 LZW 压缩,把整个文件用 "gzip" 或 "bzip2" 压缩来存储!结果会小很多,不过它需要 Web 服务器给浏览器发送正确的 'content' 和 'compression' 提示,客户端浏览器才能直接使用它。"Apache" Web 服务器默认不这么做,但可以配置成这样。更好的做法是,把整个未压缩动画目录归档成单个文件,以获得更好的存储压缩。

如果你还有其他优化点子,请告诉我。


关于 GIF 优化的其他信息来源

以上就完成了处理动画的各种基本方法与技术。不过,要形成一幅完整的图景,你应当继续阅读下一个 IM 示例页面,它详细讲解了处理真实图像动画中实际问题的技术。此外,上面许多技术都在视频转 GIF 优化的实用示例中有所演示。如果你真心认真对待 GIF 动画的处理,我还建议你彻底通读颜色量化,因为减色往往是良好处理 GIF 动画的关键。我在万维网上找到的其他关于 GIF 动画优化技术的有用来源包括……

如果你认为有某个页面我应当在此列出,请给我发邮件。我只会添加有实用内容的页面,因此不保证一定会收录你的链接。