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

ImageMagick 示例 -- 动画修改

ImageMagick 示例 前言与索引
动画的简单修改

本页包含处理 GIF 动画的实用示例。在尝试理解这些示例之前,强烈建议先阅读并理解动画基础,并且至少了解优化 GIF 动画的整体处理方式。


动画的简单修改

首先,一个重要事项

不要把尚未处理完成的中间动画直接保存为 GIF,尤其是还没有做任何半透明处理颜色优化的图像。如果你犯了把它保存为 GIF 的大错,就会让生成的动画变得更糟,因为 IM 此时会执行自动颜色量化,减少现有颜色数量,以便把图像塞进受限的 GIF 格式。不仅如此,它还会把每一帧都与其他帧完全分开处理,产生不同的颜色选择和抖动图案。也就是说,这会让之后的任何处理,尤其是任何帧优化,都变得更加困难。对于调整过尺寸的 GIF 动画,或添加了多色上层/下层叠加的动画,这一点尤其重要,因为这些操作可能会引入大量额外颜色。如果你想分步骤处理动画,可以使用 IM 内部格式 MIFF 作为临时文件格式,或者为正在编辑的每一帧使用单独的 PNG 格式图像。除非你现在确信不会有颜色问题,否则不要最终保存为 GIF。再说一遍...

不要把 GIF 用作中间文件格式,请使用 MIFF 或 PNG 图像。

标注 - 在所有帧上添加版权声明

从 IM 6.2.6 版开始,你可以直接对动画使用 "[-annotate](https://imagemagick.org/command-line-options/#annotate)",其方式类似于在图像上方标注中详细说明的做法。例如,这里我们给 previous dispose 动画加上标注,它是在动画基础中创建的。 |

  magick canvas_prev.gif -gravity center \
          -fill black     -annotate +1+1 "Copyright" \
          -fill white     -annotate +0+0 "Copyright" \
          annotate.gif
  gif_anim_montage annotate.gif annotate_frames.gif

[IM 输出]

[IM 输出]

之所以能这样做,是因为 "[-annotate](https://imagemagick.org/command-line-options/#annotate)" 会把文本相对于图像的虚拟画布来定位,而不是相对于实际图像数据来定位。因此,对动画图像而言,每一帧上的文本位置都是正确的。不过在 6.2.6 版之前,"[-annotate](https://imagemagick.org/command-line-options/#annotate)" 和许多其他图像运算符一样,会把信息和叠加内容相对于实际图像来定位,并忽略子帧可能带有的任何页面或虚拟画布偏移。需要提醒一句,如果不先合并 (Coalesce)动画就像这样在动画上绘制,可能会因为动画现有的优化方案而产生一些异常效果(见下一组示例)。因此(也如你将看到的那样),建议先通过合并 (Coalesce)去掉任何已有的帧优化和透明度优化。

绘制 - 修改合并后的动画

现在,虽然 "[-annotate](https://imagemagick.org/command-line-options/#annotate)" 会把文本相对于每帧的虚拟画布放置,但许多其他图像操作并不会。所有 "[-draw](https://imagemagick.org/command-line-options/#draw)" 操作都属于这种情况:它们只相对于实际图像绘制,完全忽略该图像在更大画布上可能具有的任何偏移。例如,这里我们在 previous dispose 动画的左上角附近画一个漂亮的绿色圆。 |

  magick canvas_prev.gif -fill LimeGreen -stroke SeaGreen \
          -draw 'circle 35,35 20,30'  draw_circle_fail.gif
  gif_anim_montage draw_circle_fail.gif draw_circle_fail_frames.gif

[IM 输出]

[IM 输出]

如你所见,"[-draw](https://imagemagick.org/command-line-options/#draw)" 画圆时参照的是“实际图像”,而不是该图像所属的更大虚拟(页面)画布。结果就像这类情况中常见的那样... 一团糟。简单的解决方案是先合并 (Coalesce)动画,再绘制,随后重新优化 GIF 动画。详见优化动画。 |

  magick canvas_prev.gif -coalesce \
          -fill LimeGreen -stroke SeaGreen -draw 'circle 35,35 20,30' \
          -layers Optimize  draw_circle.gif
  gif_anim_montage draw_circle.gif draw_circle_frames.gif

[IM 输出]

[IM 输出]

注意 IM 的动画优化器实际上决定不覆盖已经绘制过的那一部分。这比直接在实际子帧图像上绘制还要更优。用这种方法,你可以叠加任意类型的标注、版权声明或水印。当然,如果要真正把一幅图像叠加到动画的每一帧上,可能需要使用特殊的图层合成技术。如果你足够熟练,甚至可以进一步做动画合并,把动画化的版权声明叠加到你的动画上。 | _虽然这种“coalesce-optimize(合并后优化)”技术适用于大多数涉及动画的操作,尤其适用于 IM 的优化器,但有些操作会对图像造成剧烈变化,例如大幅颜色变化、阴影以及半透明处理,使得结果动画很难优化得很好。

例如,几乎任何 "[-resize](https://imagemagick.org/command-line-options/#resize)" 操作都可能因为大幅颜色变化而生成随后很难优化的动画。解决方法见下方调整动画尺寸

_
---|---

逐帧处理 - 一次修改一帧

使用 IM 的图像列表或序列运算符,你可以分别修改动画的每一帧。技巧是把每一帧提取到括号中,修改它,然后用修改后的版本替换原图像。例如,这里我们把文本作为版权水印添加到动画中,而且文本本身也作为动画出现,使其更难移除。为了不彻底破坏动画,我还使用了半透明颜色。 |

  magick canvas_prev.gif -coalesce -gravity center \
          -font Ravie -pointsize 24 -fill '#FFF8' -stroke '#0008' \
          \( -clone 0 -annotate 0 'This'        \) -swap 0 +delete \
          \( -clone 1 -annotate 0 'This'        \) -swap 1 +delete \
          \( -clone 2 -annotate 0 'image'       \) -swap 2 +delete \
          \( -clone 3 -annotate 0 'is'          \) -swap 3 +delete \
          \( -clone 4 -annotate 0 'Copy\nright' \) -swap 4 +delete \
          -layers OptimizeFrame   frame_mod.gif
  gif_anim_montage frame_mod.gif frame_mod_frames.gif

[IM 输出]

[IM 输出]

注意这里使用了括号,把 "[-annotate](https://imagemagick.org/command-line-options/#annotate)" 操作的作用范围限制在动画某一帧的简单“克隆”上。随后使用 SwapDelete 运算符,把修改后的图像放回图像序列中的正确位置。 "[-swap](https://imagemagick.org/command-line-options/#swap)" 运算符中使用单个数字的写法是在 IM v6.3.4-3 中加入的。在此之前,你需要把 "-swap 3" 写成 "-swap -1,3",才能正确工作。
这种逐帧修改技术,可能会成为你处理图像动画时遇到的最重要技术之一。你还会注意到,我实际上给第一幅和第二幅图像都添加了相同文本。上面动画中的第一幅图像是零延迟中间帧,用于定义此动画其余部分的背景。也就是说,它闪过得非常快,用户通常看不到,也并不打算让用户看到。因此,上面动画的前两个实际帧应被视为一个可见帧,而不是两个独立帧。带有非零时间延迟的帧,是一个“显示”帧序列中的最后一帧。类似地,对于其他快速运行的动画,你可能需要修改多个帧,才能让你的改动显示足够长的时间。对于绘制到所有帧上的静态标注,这并不是问题(见上面的标注示例)。这引出了关于 GIF 动画的一个重要要点。

尝试修改动画之前,先研究它。
这可能会让最终结果大不相同。

裁剪 - 限定动画区域

IM 一直努力让 "[-crop](https://imagemagick.org/command-line-options/#crop)" 图像操作相对于图像的虚拟画布而不是实际图像正确工作(IM 6.1.1 版起)。这让你可以做一些以前无法直接做到的事情。例如,裁剪 GIF 动画中的图像,并让它对所有动画仍按预期工作。 |

  magick canvas_prev.gif -crop 50x50+3+30  cropped.gif
  gif_anim_montage cropped.gif cropped_frames.gif

[IM 输出]

[IM 输出]
如你所见,裁剪成功了,就像裁剪单幅图像一样,保留了适当的偏移和页面尺寸,因此即便涉及区域变小,图像数据仍被正确定位。你也可以看到,这并没有改变整体虚拟画布尺寸! | 不要使用 "[+repage](https://imagemagick.org/command-line-options/#repage)" 从帧优化后的 GIF 动画中移除裁剪偏移。这样做也会移除把子帧定位到虚拟画布上所需的帧偏移,而后续帧可能依赖这些偏移才能正确动画。
---|---
不过,上面的 "[-crop](https://imagemagick.org/command-line-options/#crop)" 操作确实生成了一条警告消息...

[IM 文本]

这是因为动画中某一帧的裁剪区域没有包含该帧使用的子帧叠加图像。也就是说,有一帧并没有更新从动画中裁剪出来的区域。因此该帧现在不包含任何真实图像!为此,IM 不仅会生成警告消息,还会在动画中生成一个特殊的“缺失图像”作为占位符,以保持所有内容有序,并保留附着在该帧上的任何“delay”或“disposal”方法。你可以保留这个占位符,也可以按需要修复它。在这个例子中,为了让动画按预期运行,“缺失图像”是必要的。不过如果生成了多个连续的缺失图像,你大概可以使用 "[-layers](https://imagemagick.org/command-line-options/#layers)" 方法 '[RemoveDups](anim_opt.html#removedups)',把它们合并为单个缺失图像。尽管如此,仍建议谨慎并研究动画。关于这一点的更详细示例,见下方拆分动画

连画布也裁剪 - 动画的视口裁剪

就像普通裁剪会保留原图像的虚拟画布一样,对动画的裁剪也会保留虚拟画布。在这种情况下,这可能并不是你想要的效果。正因如此,IM 6.2.4-5 版加入了一个特殊标志 '!',用于 "[-crop](https://imagemagick.org/command-line-options/#crop)" 参数。这个标志会让裁剪不仅裁剪各个图像帧,还把图像的页面或画布信息调整到同一区域。这称为“视口裁剪”,因为结果就像是你透过一个大小和位置由裁剪参数指定的“窗口”或“视口”观看图像。不仅虚拟画布的尺寸会设为裁剪区域的尺寸,动画中每一帧的偏移也会调整,以保持正确。(见带画布/页面调整的视口裁剪)。例如,让我们重复前一次裁剪,但这次也使用 '!' 标志裁剪画布信息... |

  magick canvas_prev.gif -quiet -crop 50x50+3+30\!  crop_viewport.gif
  gif_anim_montage crop_viewport.gif crop_viewport_frames.gif

[IM 输出]

[IM 输出]
| '!' 字符对某些 UNIX shell(例如 "csh")有特殊意义,即使放在引号中,也必须用反斜杠转义。IM 会忽略几何参数中的反斜杠,所以总是加上反斜杠也没有坏处。
---|---
如你所见,结果更接近你裁剪动画图像时真正想要达到的效果。注意我加入了 "[-quiet](https://imagemagick.org/command-line-options/#quiet)" 设置,要求 IM 不要输出前一次裁剪尝试中生成的缺失图像警告。裁剪动画时建议这样做,因为该警告实际上并不适用。还要注意,视口裁剪也允许你扩大画布区域,甚至重新定位画布内的所有内容。不过它也有风险:任何部分或完全落在裁剪区域之外的图像,都会被裁到只显示该区域内出现的部分。最后再提醒一句。使用“视口裁剪”时,帧图像会沿与“视口”给定偏移相反的方向移动。除非你记得裁剪运算符中的偏移表示的是视口位置,而不是直接重新定位图像本身,否则这看起来可能不合逻辑。

边界修剪 - 自动修正画布尺寸

和前面的操作一样,修剪动画可能会有些棘手。如果动画由简单的清除帧动画组成,那么只要算出动画内所有单独帧的最大边界,就可以修剪动画。从 IM v6.3.4-8 开始,你可以用 'TrimBounds' 图层方法非常轻松地做到这一点。

  magick anim_bgnd.gif -layers TrimBounds anim_trim_bounds.gif

[IM 输出] [IM 输出]

对于使用更早 IM 版本的用户,仍然可以做同样的事,但需要分两步完成(同时还会执行其他不想要的处理)。为此,你可以使用图层合并把动画所有帧合并成一个单一图层,然后让 IM 报告该图层的尺寸和偏移...

  magick anim_bgnd.gif -layers merge -format '%wx%h%X%Y' info:

[IM 文本]

既然你已经知道所有帧的边界,只需把整个动画视口裁剪到这个尺寸。

  magick anim_bgnd.gif -crop 89x77+5+10! anim_trim_crop.gif

[IM 输出] [IM 输出]

如果你还想从动画中修剪静态背景,那么最好的做法是先从帧优化动画中删除第一帧,再使用图层合并步骤。随后可以把返回的边界用于原始动画的视口裁剪

重新定位帧

一个相似且相关的操作是“相对 repage”运算符。它会把给定偏移添加到动画中所有单独的子帧图层上,让你可以相对于整个画布调整它们的位置。要让 "[-repage](https://imagemagick.org/command-line-options/#repage)" 操作变成相对操作,也需要给它的参数添加 '!' 标志。例如,这里我们把动画的第二帧及后续帧向下、向右移动 30 像素,同时把第一个“背景”帧恢复到正常的 '+0+0 位置。 |

  magick canvas_prev.gif -repage 0x0+30+30\! \
           \( -clone 0 -repage +0+0 \) -swap 0,-1 +delete \
           repage_offset.gif

  magick identify repage_offset.gif

[IM 输出]
| [IM 文本]


| 上面的动画在 Windows Internet Explorer 8 中会失败(只显示前两帧)。只要某一帧试图把图像绘制到动画画布边界之外,就会发生这种情况。
---|---
注意,没有任何图像被“裁剪”或切掉。只是它们相对于原始背景图像的位置发生了变化,即使图像被移到了“画布之外”也是如此。如果愿意,你也可以扩展画布以匹配这些新边界,可以直接调整画布尺寸... |

  magick repage_offset.gif -repage 130x130  repage_canvas.gif

[IM 输出]
通过使用边界修剪图层方法,你可以自动扩展动画边界,刚好足以包含那些现在被放到“越界”位置的图像... |

  magick repage_offset.gif -layers TrimBounds repage_bounds.gif

[IM 输出]
| _使用 "[-repage](https://imagemagick.org/command-line-options/#repage)" 把图像向左或向上移动,尤其是在小画布上移动,很可能会让 GIF 动画失败。这个格式基本上不能使用负的图像偏移。

为此,你最好同时应用“视口裁剪”,或者使用“边界修剪”把所有偏移移入一个更大的正向画布。任一方法都能保证所有图像帧具有正偏移。

PNG 和 MNG 格式可以处理负偏移,但许多 Web 浏览器和其他程序可能不理解这类偏移,从而产生奇怪效果。例如,某个版本的 "Firefox" Web 浏览器在尝试显示带负偏移的 PNG 时,会生成极大的图像。_
---|---

反转动画 - 让动画倒着播放,或循环

从 IM v6.3.3 起,加入了 "[-reverse](https://imagemagick.org/command-line-options/#reverse)" 图像序列运算符(更多细节见 Reverse 运算符)。这让你可以非常简单地反转合并后动画序列的顺序。例如,这里我让一个“手绘 k”的动画变成被擦回去! |

  magick script_k.gif -coalesce -reverse \
          -quiet -layers OptimizePlus  -loop 0 reversed.gif

[IM 输出]
我必须重新给上面的命令添加 "[-loop](https://imagemagick.org/command-line-options/#loop)" 选项,因为它需要附着在第一幅图像上,而现在第一幅图像已经变成最后一幅了!结果也许还需要做些时序调整,但你可以看到,它现在把字母“画没了”!反转图像序列之前一定要先 "[-coalesce](https://imagemagick.org/command-line-options/#coalesce)",因为现有的任何帧优化都依赖图像顺序。最好先去掉这些优化。

巡逻循环 - 在两端之间来回循环

一种类似技术是在动画末尾添加反向顺序的帧,让结果动画在原动画的第一帧和最后一帧之间循环往返。这有点像守卫在两点之间巡逻,因此称为“巡逻循环”。这里我使用图像Duplicate 运算符(加入于 IM v6.6.8-7)生成额外的(反向)帧。 |

  magick script_k.gif -coalesce   -duplicate 1,-2-1 \
          -quiet -layers OptimizePlus  -loop 0 patrol_cycle.gif

[IM 输出]
注意,我并没有直接复制动画的所有图像,而是跳过了原始序列的第一幅和最后一幅图像。如果我复制了所有图像,第一幅和最后一幅图像会显示两倍于预期的时间,并让动画文件比需要的更大。即便如此,你仍然应该留意动画开头和结尾处的零延迟中间帧,因为它们可能带来意外问题。基本上,不先研究动画就这样做,就是在自找麻烦。另外,为了让结果能更好地优化,你甚至可能需要在正向和反向循环之间添加一些额外的零延迟中间帧,以改善优化。这些额外帧在原动画优化中可能并不存在,因为整个动画通常会在循环时重置。关于这些额外帧如何帮助优化和改善动画,详见拆分帧更新。下面是使用 Clone 运算符生成重复帧的旧方法。 |

  magick script_k.gif -coalesce \( -clone -2-1 \) \
          -quiet -layers OptimizePlus  -loop 0 patrol_cycle_2.gif

[IM 输出]

颜色变形 - 两幅图像之间的动画变化

"[-morph](https://imagemagick.org/command-line-options/#morph)" 运算符尤其有趣。它会接收一组图像,并在它们之间插入额外帧,从而从一幅图像到下一幅图像做柔和的颜色变化。不过这个运算符不是真正的“morph”,因为它只是修改像素颜色,生成一系列混合图像。真正像电影那样的“morph”还涉及图像扭曲,把图像中对象的轮廓变换到另一幅图像中的对象。例如,这里我用颜色变形在 rose 图像和它的翻转形式之间生成额外帧,从而创建一个巡逻循环。 |

  magick -delay 20 rose: \( +clone -flip \)  -morph 5 \
          -duplicate 1,-2-1  rose_flip.gif

[IM 输出]
这并不特别理想,因为所有图像都有相同延迟。结果就是动画在循环的两个端点之间似乎从不“休息”或暂停。更好的方案是在原图和它的“翻转”形式上加入暂停。不过这需要你把原始图像的延迟调整为不同于变形过程中的图像。 |

  magick rose: \( +clone -flip \)  -morph 5 -set delay 10 \
          \( -clone 0 -set delay 240 \) -swap 0 +delete \
          \( +clone   -set delay 240 \) +swap   +delete \
          -duplicate 1,-2-1 rose_flip_pause.gif

[IM 输出]
如你所见,要生成好的动画,时序延迟会变得非常重要,它能让动画在恰当位置“停留”。从 IM v6.6.9 起,你可以使用基于图像索引计算的 FX 百分号转义来设置延迟。这里的 FX 表达式表示:如果图像索引不是第一幅(t=0)也不是最后一幅(t=n-1),就使用 10 的延迟,否则使用更大的值。 |

  magick rose: \( +clone -flip \)  -morph 5 \
          -set delay '%[fx:(t>0&&t<n-1)?10:240]' \
          -duplicate 1,-2-1    rose_flip_anim.gif

[IM 输出]
关于从一幅图像“变形”或“转场”到另一幅图像的各种不同方法,可参见 Fred Weinhaus 的 "[transitions](http://www.fmwconcepts.com/imagemagick/transitions/)" 和 "[fxtransitions](http://www.fmwconcepts.com/imagemagick/fxtransitions/)" ImageMagick shell 脚本。示例页面中包含脚本用于生成动画的基本算法。

尺寸变形 - 尺寸的动画变化

颜色变形运算符实际上不仅会在两幅图像之间做颜色混合,还会同时调整图像尺寸。例如,这里我对两幅尺寸不同、甚至纵横比也不同的图像使用 "[-morph](https://imagemagick.org/command-line-options/#morph)"。 |

  magick rose: medical.gif -morph 10 \
          -layers TrimBounds -set dispose previous -coalesce \
          -background black -alpha remove \
          -set delay '%[fx:(t>0&&t<n-1)?10:60]' \
          -duplicate 1,-2-1  -loop 0  morph_resize.gif

[IM 输出]
只有第一行执行尺寸变形。如果你查看实际图像,会发现每一帧都有不同尺寸!接下来的两行会把图像“填充”为相同尺寸,并用黑色填充未使用的部分。具体来说,这些操作被设计成不依赖图像顺序。剩下的只是设置巡逻循环及相关时序延迟。“调整尺寸”只从左上角开始进行。在撰写本文时,颜色变形运算符不了解图层偏移,也不了解任何其他空间变形(扭曲变形)。因此,如果你想让尺寸变化居中,可能需要使用后面章节展示的复杂得多的技术。这里是一个类似示例,这次用同一图像的较小版本来调整图像尺寸(保持纵横比)... |

  magick rose: \( +clone -resize 10 \) -morph 10 \
          -layers TrimBounds -set dispose previous -coalesce \
          -background black -alpha remove \
          -set delay '%[fx:(t>0&&t<n-1)?10:60]' \
          -duplicate 1,-2-1  -loop 0  morph_resize_self.gif

[IM 输出]
注意,“中间”图像比它们本应呈现的样子更加模糊。这是因为较大的图像不仅被缩小,还与被放大的较小图像进行了颜色混合。

划像转场 - 从一幅图像过渡到另一幅图像

这其实非常容易。只需叠加新图像的“细条”即可。这些细条直接使用简单的平铺裁剪生成。例如,这里我们从一幅图像以划像方式过渡到它的翻转版本,并且为了好玩,再划像转场回来。 |

  magick rose: \( -clone 0 -flip -crop 3x0 \) \
                \( -clone 0 -crop 3x0 \) \
                -set delay 10 -loop 0  wipe.gif

[IM 输出]
这里是 IM 论坛上 GeeMack 提供的一个版本,实现了来自四个方向的划像转场... |

  magick -size 100x60 -delay 100 \
      gradient:green-yellow gradient:blue-purple \
      gradient:orange-white gradient:red-black \
      -write mpr:stack -delete 0--1 \
      \
      mpr:stack'[0]' \( mpr:stack'[1]' -set delay 5 -crop 4x0 \) \
      mpr:stack'[1]' \( mpr:stack'[2]' -set delay 5 -crop 0x4 \) \
      mpr:stack'[2]' \( mpr:stack'[3]' -set delay 5 -crop 4x0 -reverse \) \
      mpr:stack'[3]' \( mpr:stack'[0]' -set delay 5 -crop 0x4 -reverse \) \
      -loop 0 wipe_all.gif

[IM 输出]

动态扭曲 - 基于图像索引扭曲多幅图像

许多运算符可以在参数中使用百分号转义。这意味着你实际上可以修改运算符,让它对正在处理的每幅图像执行略有不同的动作。方法是先复制图像,创建 30 个(或你喜欢的任意数量)完全相同的 rose 图像副本。然后使用 FX 百分号转义按图像索引 '%[fx:t]' 和列表中的图像数量 '%[fx:n]' 计算扭曲值,从而以不同方式修改每幅图像。例如,这里我按计算出的量平移图像。 |

  magick rose: -duplicate 29  -virtual-pixel tile \
          -distort SRT '0,0 1, 0, %[fx:w*t/n],%[fx:h*t/n]' \
          -set delay 10 -loop 0     rose_diagonal_roll.gif

[IM 输出]
这里我根据索引旋转图像,但如果图像索引为 0(第一幅图像),就生成更长的暂停。 |

  magick rose: -duplicate 29  -virtual-pixel Gray \
          -distort SRT '%[fx:360*t/n]' \
          -set delay '%[fx:t==0?240:10]' -loop 0     rose_rotate.gif

[IM 输出]
注意图像索引('t')的值从 '0' 到 'n-1',因此公式 '%[fx:t/n]' 的值会从 '0.0' 到略小于 '1.0'。这非常适合上面那样的重复或循环动画,但对于从一幅图像过渡到新图像,可能并不理想。在那种情况下,你希望最终帧的乘数为 '1.0',就应使用公式 '%[fx:t/(n-1)]'。这只是现在可以用 '%[fx:...]' 计算中的图像索引轻松完成的一个小例子。想象一下更复杂的扭曲还能做些什么。如果不使用图像索引计算,上面的效果就需要外部 shell 循环逐帧生成,并且还要单独一步收集这些帧形成最终动画。简单的变形图像动画中给出了这类循环 shell 脚本的示例,因为那些运算符不允许在参数中使用百分号转义。 | 在 IM v6.6.9-0 之前,涉及图像索引的百分号转义FX 百分号转义,例如 '%p'、'%n'、'%[fx:t]' 和 '%[fx:n]',都无法正常工作。它们通常只返回 '0' 或 '1' 这类无用值,而不是当前图像序列中的实际索引和图像数量。
---|---

附加标签 - 给整个动画添加标签

一如既往,实际给图像附加标签的方法有很多。例如,对于带初始背景画布的动画,或者只向前面帧叠加新颜色的动画,你可以只把标签附加到图像的第一帧。其他帧不会移除它。这里我们用 "[-splice](https://imagemagick.org/command-line-options/#splice)" 添加一些额外空间,并用 "[-annotate](https://imagemagick.org/command-line-options/#annotate)" 在其中写入一些文本。 |

  magick canvas_prev.gif \
          \( -clone 0 -coalesce -gravity South -background white \
             -splice 0x18 -annotate 0 'Label First' \) \
          -swap -1,0 +delete   label_first.gif

[IM 输出]
不过这只适用于某些动画。它不适用于常见的清除帧动画,因为这类动画在每帧显示后会清除或替换所有像素。要得到适用于所有动画的更通用方法,我们需要先把动画 "[-coalesce](https://imagemagick.org/command-line-options/#coalesce)" 成未优化的合并动画。然后就可以在重新优化之前,把标签添加到动画每一个合并后的帧上。 |

  magick canvas_prev.gif -coalesce \
          -gravity South -background white -splice 0x18 \
          -annotate 0 'A Better Label' \
          -layers Optimize labeled_anim.gif

[IM 输出]
除了使用 "[-annotate](https://imagemagick.org/command-line-options/#annotate)" 在新增的额外空间里绘制文本,你也可以使用合成方法(见下一节)把图像合成进这个空间。这样你就可以准备一个更精致的标签添加到动画中。当然,这样做会导致某些动画之后不太容易优化,尤其是清除帧动画,但这是添加标签要付出的代价。对于这种类型的动画,一个解决方案是在前面预置一个包含标签的“初始背景画布”,如讲解清除帧动画的章节所示。另请注意,给动画添加标签可能会引入许多额外颜色。这可能超过 GIF 的颜色限制,因此你也可能必须准备对动画做颜色优化。这是一个非常困难的任务,能避免最好(见颜色优化)。这可能是任何动画上的任何通用修改都会遇到的问题。

移除透明度 - 添加纯色背景

你在 Web 上找到的大量动画都有透明背景。这很有用,因为你可以把它们放到网页上,而不需要担心可能存在的任何背景图案。不过在处理动画时,尤其是在应用 "[-resize](https://imagemagick.org/command-line-options/#resize)" 和 "[-blur](https://imagemagick.org/command-line-options/#blur)" 等其他图像运算符时,这样的动画会有问题。通用解决方案是从图像中移除透明度,通常是以某种方式把它们叠加到一个特定颜色上,例如网页背景使用的特定颜色。[IM 输出] 例如,这里我有一个简单的透明叠加动画,像是由看不见的手写出字母“K”。由于这个 GIF 动画使用透明度绘制,并且只把图像叠加到前面的帧上(添加像素,从不移除像素),设置背景色(或背景图像)的简单方法就是只把它添加到动画的第一帧。所有其他帧在背景处都包含透明色,所以不会影响结果。这里我们使用扁平化运算符,把动画第一帧叠加到 'LimeGreen' 背景色上。我们可以在这里使用 "[-flatten](https://imagemagick.org/command-line-options/#flatten)",因为它只应用到单幅图像,而不是整个动画。 |

  magick script_k.gif \( -clone 0 -background LimeGreen -flatten \) \
          -swap 0,-1 +delete      script_k_flatten_0.gif

[IM 输出]
还需要注意,原始第一帧只有一个像素大小。扁平化运算符不仅给背景上色,还把该帧扩展到完整尺寸。也就是说,它也“填满”了这一帧。不过请注意,只有动画的第一帧被上色。这个方法是首选,因为任何优化(比如这个动画包含的重度优化)都会被保留下来。给第一帧上色并不适用于所有 GIF 动画。它只适用于简单的叠加动画。如果想用通用方法从动画中移除透明度,需要先 "[-coalesce](https://imagemagick.org/command-line-options/#coalesce)" 动画,然后实际从所有帧中移除透明度,使用 Alpha Remove 运算符。这次我们用 'Tomato' 背景色来做。 |

  magick script_k.gif -coalesce   -background Tomato -alpha remove \
          -layers Optimize   script_k_alpha_rm.gif

[IM 输出]
当然,得到的优化结果可能不如原始动画那样理想,但动画中不再有任何透明度。另一个副作用是,动画中的任何 '[Background](anim_basics.html#background)' 处置设置都会转换为 '[None](anim_basics.html#none)' 或 '[Previous](anim_basics.html#previous)',这是由帧优化过程完成的,因为透明度不再是问题。 | Alpha Remove 是 IM v6.7.5 加入的。如果你的 IM 版本更旧,需要使用替代方法,例如利用边框运算符的副作用。有关这种方法和其他方法的细节,见移除透明度
---|---
更复杂的背景处理,例如在下方衬入背景图像或图案,需要比我们目前看到的简单修改复杂得多的动画处理。这就是接下来要看的内容。


多图像 alpha 合成

下一层级的动画处理要求你能够把单幅静态图像合成到现有动画的上方或下方。也就是通用的 Alpha 合成。当两个独立图像集合被合并时,这会变得更棘手。在 IM v6.3.3-7 之前,多列表合成只能通过专门设计的 API 脚本,或通过保存并合并动画单独帧的 shell 脚本来完成。两种都不算好技术,但当时只能如此。现在,这已经改变了。

绘制图像 - 把图像绘制到图像列表上

"[-draw](https://imagemagick.org/command-line-options/#draw)" 运算符能够把一个 图像合成到一组图像上方。它也是 IM v6.3.3-7 之前,你能在 "[mogrify](basics.html#mogrify)" 命令中使用,或针对多幅图像使用的唯一多图像 alpha 合成方法。这个 Alpha 合成技术之所以如此重要,是因为它允许你把一幅图像作为与当前图像列表分开的参数来指定。也就是说,在带引号的 Magick Vector Graphic 语言内部,即 "[-draw](https://imagemagick.org/command-line-options/#draw)" 中指定。由于它有历史重要性,我会详细展示它的用法,尤其面向仍在使用较旧 IM 版本的用户。例如,这里我把 rose 图像叠加到整个动画上。 |

  magick canvas_prev.gif -coalesce \
          -gravity NorthEast  -draw 'image over 5,5 0,0 "rose:"' \
          -layers Optimize   draw_over.gif

[IM 输出]
这允许你把外部 图像合成到当前图像序列中的每幅图像上。对大多数用途来说已经足够。例如,使用 '[Dst_Over](compose.html#dstover)' 合成方法,你也可以把图像作为静态背景放在动画“下面”。例如,这里我们把一个 "netscape:" 内置图像“下衬”进去,当然它也可以是任何外部图像文件... |

  magick script_k.gif -coalesce \
              -draw 'image DstOver 0,0 0,0 "netscape:"' \
          -layers Optimize   script_k_netscape.gif

[IM 输出]
注意动画尺寸没有改变,因为 alpha 合成的最终尺寸由 目标 图像定义。如果确实想创建更大的画布,就必须适当调整动画的尺寸和偏移,以容纳背景。例如,在合并之前对动画使用相对 repage。 |

  magick script_k.gif  -repage 100x100+20+20\!   -coalesce \
              -draw 'image DstOver 0,0 0,0 "granite:"' \
          -layers Optimize   script_k_granite.gif

[IM 输出]
另外,如果你想使用已经读入、创建或修改过的图像,就需要使用 MPR: Memory Program Register 来为该图像提供一个“读取源”。 |

  magick -size 53x54 xc:SkyBlue -fill DodgerBlue \
          -draw 'circle 26,27 24,8' -write mpr:bgnd +delete \
          \
          script_k.gif -coalesce \
          -draw 'image DstOver 0,0 0,0 "mpr:bgnd"' \
          -layers Optimize   script_k_mpr_bg.gif

[IM 输出]
这差不多就是 Draw Alpha 合成方法的极限了。无法把动画图像叠加到尺寸未知的 目标 图像“上方”,也没有办法把两个独立的多图像序列合并在一起。直到...

图层合成 - 图像列表的 alpha 合成

在 IM v6.3.3-7 中,加入了 "[-layers](https://imagemagick.org/command-line-options/#layers)" 方法 '**Composite**',允许你把两个完全独立的图像集合合成在一起。(简短总结见图像分层,图层合成)要在命令行上做到这一点,需要一个特殊的 '[null:](files.html#null)' 标记图像,用来定义第一组 目标 图像列表在哪里结束,被叠加的 图像列表在哪里开始。但这也是这个方法唯一真正复杂之处。于是让我们试一试:从一组图像创建一组阴影,然后把原始图像叠加到这些阴影图像上... |

  magick script_k.gif -coalesce  coalesced_k.gif

  magick coalesced_k.gif  -background black -shadow 100x3+2+5 \
          -background SkyBlue -alpha remove    shadows_k.gif

  magick shadows_k.gif  null:  coalesced_k.gif \
          -layers Composite          compose_shadow.gif

  gif_anim_montage compose_shadow.gif compose_shadow_frames.gif

[IM 输出]
[IM 输出]
[IM 输出]

[IM 输出]

上面的示例非常重要,所以我会详细解释。首先,我们生成动画的合并版本,以移除可能存在的任何优化,让动画准备好进行严肃处理,而不会被 GIF 优化干扰。接着,我们从合并后的动画创建一个动画阴影图像,并移除透明度,因为 GIF 无法处理半透明阴影。这就是我们想加到原动画“下面”的动画。它拥有与原动画相同的帧数,甚至相同的时序。这种对应关系很重要,不要忘记。现在我们读入两个动画或图层序列,但在它们之间加入特殊的 '[null:](files.html#null)' 图像分隔符,让 ImageMagick 知道一个序列何时结束、下一个序列何时开始。这个图像分隔符会被接下来至关重要的 "-layer composite" 操作自动移除。其他 API 应该能够使用单独的图像“Wand”,而不是带特殊分隔符的单一序列。随后图层合成会像处理简单的单幅图像一样处理这两个动画或图像序列,而不是把它们看成多幅图像的序列。每一对图像(一个目标、一个源)被合成在一起,生成一个合并(合成)后的图像序列。最终结果是,我们给原始动画序列添加了阴影,它已经可以进行 GIF 优化,或直接使用。
现在,你可以用一条命令完成上面所有步骤。不过不能简单使用 "[-clone](https://imagemagick.org/command-line-options/#clone)" 创建原始序列的副本,因为我们并不知道(也不想知道)序列中有多少幅图像。你可以改用 MPR: Memory Program Register 保存整组图像列表。它有点像给当前内存中的整个图像序列拍一张快照,然后稍后再把它读回来。结果是类似下面的命令,不过我使用了不同颜色的背景。 |

  magick script_k.gif -coalesce -write mpr:images \
          -background black  -shadow 100x3+2+5 \
          -bordercolor Wheat -border 0 \
          null:    mpr:images    -layers Composite \
          magick composite_shadow.gif

[IM 输出]
这个版本实际效果更好,因为我们没有丢失阴影运算符生成的偏移信息(GIF 无法保存负偏移,所以会把它重置为零)。上面的例子可以通过把中间图像改用 MIFF 文件格式而不是 GIF 来修正,或者如你将在下一个示例中看到的那样,使用 "[-geometry](https://imagemagick.org/command-line-options/#geometry)" 合成偏移。基本上,这些示例表明图层合成运算符确实理解单独的虚拟画布偏移("[-page](https://imagemagick.org/command-line-options/#page)" 设置),并会像图层扁平化,或者更好的图层合并运算符那样处理它们。但图层合成运算符也理解合成几何("[-geometry](https://imagemagick.org/command-line-options/#geometry)")偏移(默认值为零),用来控制整个叠加图像序列的整体位置。它甚至理解 "[-gravity](https://imagemagick.org/command-line-options/#gravity)" 对这个全局偏移的影响。例如... 让我们把原来的“K”动画叠加到生成的阴影动画的“South”位置... |

  magick shadows_k.gif  null:  coalesced_k.gif  \
          -geometry +0-10 -gravity South  -layers Composite \
          magick composite_south.gif

[IM 输出]
上面也展示了,与普通的双图像 Alpha 合成一样,控制最终输出图像尺寸的是目标图像序列,任何合成叠加都会被裁切到目标画布图像内。因此你应确保所有目标图像都足够大,能够容纳最终结果。 | "[-geometry](https://imagemagick.org/command-line-options/#geometry)" 的调整尺寸能力严格来说不是合成操作的一部分,它只会调整当前图像序列最后一幅图像的尺寸。因此,如果与多图像图层合成一起使用,它不会按你的预期工作。详见尺寸调整几何
---|---
基本上,图层合成非常像普通合成。
真的相当简单。我说简单了吗?
图层合成细节......如你在上面看到的,命令行版 "[-layers](https://imagemagick.org/command-line-options/#layers) Composite" 会使用当前图像列表中找到的第一个 '[null:](files.html#null)' 图像作为标记,把两个列表分开。两个图像列表会被分离,'null:' 会被丢弃,然后两个列表会一次两幅图像地进行 Alpha 合成。只有由特殊 '[null:](files.html#null)' 图像源生成的图像才能用作标记;如果找不到就会报告错误。目前你不能从管道读入这个 '[null:](files.html#null)' 标记图像(至少目前不能),只能在需要时生成它。图层合成也比简单的双图像 Alpha 合成复杂一些,因为图像列表中的图像虚拟画布也会被考虑在内。通常 alpha 合成为了定位会忽略任何虚拟画布尺寸和偏移,只使用图像的实际尺寸。这个特殊图层方法会使用虚拟画布信息来进行几何定位,以便对齐两个图像序列。因此,子帧带有的任何虚拟画布偏移也会加到通常经过 "[-gravity](https://imagemagick.org/command-line-options/#gravity)" 调整的 "[-geometry](https://imagemagick.org/command-line-options/#geometry)" 合成偏移上,用来计算图像叠加的位置。只有每个列表第一幅图像的虚拟 "[-page](https://imagemagick.org/command-line-options/#page)" 画布尺寸会用于计算 "[-gravity](https://imagemagick.org/command-line-options/#gravity)" 对 "[-geometry](https://imagemagick.org/command-line-options/#geometry)" 合成偏移所做的调整。后续图像的画布尺寸会被忽略,只有各自的虚拟 "[-page](https://imagemagick.org/command-line-options/#page)" 偏移会加入计算出的 "[-geometry](https://imagemagick.org/command-line-options/#geometry)" 偏移。换句话说,"[-layers](https://imagemagick.org/command-line-options/#layers) Composite" 是为“图层”或“动画”的 alpha 合成,以及这类图像列表的特殊需求而设计的。注意事项...不过,对于你要叠加的图像列表,仍然需要小心。例如,如果 目标 列表图像不够大,或位置不正确,无法包含叠加的 图像,那么叠加图像就会被裁切,或者完全错过目标图像。因此,在叠加较小的 图像之前,最好先把 目标 图像合并 (Coalesce)到完整画布尺寸。例如,见下方并排动画追加示例,那里需要扩展画布尺寸来为追加图像提供空间。另外,如果 图像列表是 GIF 动画,那么你可能需要确保子帧已经清除了压缩优化和复杂帧优化等内容,否则可能会遇到问题。另一方面,清除帧动画合并动画可以直接 '[Composite](#composite)',没有任何问题。只要记住,图层合成不了解图像中可能存在的任何 GIF 处置方法,不过它会保留 目标 GIF 动画元数据,例如:处置方法帧延迟迭代循环限制。唯一例外会在下面的特殊情况中给出。

单图像合成 - 用单幅图像合成图像

通常,两个长度相同的图像列表会成对合成,一次一对,直到其中任一列表耗尽。两个图像列表都不会重复。合成会直接停止。留下来的只是原始 目标 图像列表,外加已添加的合成结果。'[null:](files.html#null)' 分隔图像以及所有 图像都会从当前图像列表中删除。 此图层方法的 API 接口应该允许你生成两个独立的图像列表,并由你负责删除用于生成结果图像列表的两个输入图像列表。'[null:](files.html#null)' 分隔符应该不再需要。
不过,如果其中一个列表只包含单幅图像,那么这幅图像会与另一个列表中的所有图像合成。这个单幅图像是 图像还是 目标 图像并不重要。该方法会与另一个图像列表进行合成,并保留 图像列表 的 GIF 元数据,而不是单幅图像的元数据,即使那幅单图像是 目标 图像也是如此。这种“与单幅图像合成”是图层合成的特殊情况,非常适合给动画添加背景(见下一节),或向动画中插入静态对象。

静态背景 - 合成到更大的背景上

例如,使用这个特殊的单图像图层合成方法,我们可以把动画合成到静态背景上... |

  magick -size 100x100 plasma:fractal null: \( script_k.gif -coalesce \) \
              -gravity Center   -layers Composite \
          -layers Optimize   magick composite_background.gif

[IM 输出]
由于背景图像是 目标,它定义动画的最终尺寸,但所有元数据(延迟、标签、注释等)都来自源图像列表。通常这些信息来自目标图像列表。这是图像合成期间唯一由源图像提供元数据信息的情况。还要注意,由于图层合成理解 "[-gravity](https://imagemagick.org/command-line-options/#gravity)",图像会正确居中,不需要你自己计算。不过,如果源帧包含偏移,这些偏移也会加到由 gravity 定义的位置上,从而保持所有子帧的相对位置正确。注意,动画 "[script_k.gif](../static/img/images/script_k.gif)" 实际上是一种叠加动画,因此也有其他方法给它添加静态背景。示例见上面关于移除透明度的章节(那里是合成到纯色上,但也可以是任何图像)。对于更简单的清除帧动画也是如此。在那种情况下,你甚至不需要先合并 (Coalesce)动画,而可以直接把它合成到背景图像上。不过之后你可能需要 "[-set](https://imagemagick.org/command-line-options/#set)" 所使用的 'dispose' 方法,或者更好地,优化这个完全合并动画。然而,任何其他类型的优化动画都需要执行 "[-coalesce](https://imagemagick.org/command-line-options/#coalesce)" 操作,并对所有动画帧进行完整合成。因此,为了确保所有 GIF 动画都能正确处理,最好还是使用上面的方法。


闪闪发光的一切...

闪粉动画

上面的图层合成方法让生成闪粉这类简单动画容易得多。首先,我们需要足够大、能覆盖待处理图像的闪粉。这里我会从一些随机斑点图像生成一个三帧闪粉动画。首先,这是纯透明上的原始黑白闪粉,通过把三个颜色通道分离成黑白通道图像生成 3 帧闪粉。它基本上是生成任何其他类型闪粉的原始起点。'30%' 阈值控制每帧有多少“点”。 |

  magick -size 100x100 xc: +noise Random -separate \
          null: \( xc: +noise Random -separate -threshold 30% -negate \) \
              -compose CopyOpacity -layers composite \
          -set dispose background -set delay 20 -loop 0   glitter_overlay.gif

[IM 输出]
从这个“原始”闪粉出发,你可以使用 '[Screen](compose.html#screen)' alpha 合成把它叠加上去,只让某些颜色变亮,从而生成特定颜色的闪粉。我使用上面的边框扁平化方法。只是一个纯色... |

  magick glitter_overlay.gif \
          -compose Screen -bordercolor GoldenRod -border 0x0  glitter_gold.gif

[IM 输出]
使用图层合成,你还可以用单幅图像,甚至多幅图像,提供可变颜色背景。例如,这里我生成三幅分形等离子图像,为闪粉图案提供略带随机的着色。 |

  magick glitter_overlay.gif null: -size 100x100 \
          plasma:red-firebrick plasma:red-firebrick  plasma:red-firebrick \
          -compose Screen -layers composite    glitter_plasma.gif

[IM 输出]
当然,还有许多其他闪粉样式和运动图案。你可以在 WWW 上找到并下载很多这样的闪粉平铺图。要把这样的闪粉应用到图像上,有几种不同方法。通常,你会把闪粉遮罩到特定形状和/或背景上。为此,可以使用透明形状(用 DstIn 合成)

  magick -size 100x100 -fill white -background none -font Candice \
          -gravity center -pointsize 90 label:A   glitter_mask_trans.gif
  magick glitter_plasma.gif null: glitter_mask_trans.gif -alpha set \
          -compose DstIn -layers composite        glitter_masked_trans.gif

[IM 输出] [IM 输出]

或者使用黑白遮罩图像(用 CopyOpacity 合成)

  magick -size 100x100 -fill white -background black -font Candice \
          -gravity center -pointsize 90 label:A    glitter_mask.gif
  magick glitter_plasma.gif null: glitter_mask.gif -alpha off \
          -compose CopyOpacity -layers composite   glitter_masked.gif

[IM 输出] [IM 输出]

好了,我们已经有一个被遮罩的区域,可以完成图像了,通常做法是把遮罩后的闪粉叠加到原始图像上。不过在这个例子中,我会下衬一个背景,并在上方叠加边框。 |

  magick glitter_masked.gif -size 100x100 \
          null: gradient:gold1-gold4 -compose DstOver -layers composite \
          null: \( -fill none -background none -stroke white -strokewidth 2 \
                   -font Candice -gravity center -pointsize 90 label:A \) \
              -compose over -layers composite      glittered_letter.gif

[IM 输出]
最后这个示例还通过从最终图像中移除所有透明度,并在闪粉区域周围叠加平滑边框,清理了所有 GIF 透明度问题。 | 虽然上面我可能使用了 GIF 格式图像,以便用 magick display 显示处理过程中的各个步骤,但实际使用中,你应该把所有步骤合并为一个命令,或者使用 MIFF 这类更好的中间图像文件格式。也就是说,在完成之前,避免 GIF 格式固有的问题。
---|---

闪粉平铺图 - “图像中的洞”的下衬

如前所述,WWW 上有很多预先准备好的动画闪粉平铺图(搜索 "glitter tiles")。一个来源是 IM Studio 用户 scri8e 及其网站 Moons Stars。不过请注意,我觉得大多数闪粉平铺图都相当难看,或者太快。 在这个示例中,我找到并修改了一个带有小星星图案的蓝色闪粉平铺图。我认为它适合给 IM 巫师一件闪闪发光的衣服,让他看起来真的很有魔力。给现有图像加闪粉最简单的方法,大概是直接在图像中挖洞,而不是试图把闪粉图案遮罩出来。不过这只适用于一开始不含透明度的图像。另一种做法是先从图像中移除透明度,完成后再重新加回原始透明度。那么让我们拿 IM 示例的标志,用颜色替换切掉图像中的所有蓝色部分。差不多像是给我们的巫师披上一件隐形斗篷 ;-)

  magick logo.gif -alpha set -fuzz 33% -transparent blue logo_holed.gif

[IM 输出] [IM 输出]

注意这里使用了模糊系数,用来调整到底要移除多少蓝色。不过要提醒的是,这并不是从图像中切出区域的好方法,因为它会产生锯齿边缘。目前还没有简单的羽化裁切功能。好了,我们有了一幅带洞(或许多洞)的图像。下一步是把闪粉平铺图衬在下面。问题是,上面的平铺图太小了,无法覆盖整幅图像!下面使用一个技巧来平铺多图像闪粉平铺图。不过你仍需要给出比原始图像更大的尺寸,以确保能完全覆盖它。 |

  magick glitter_blue.gif -virtual-pixel tile \
          -set option:distort:viewport 180x180 -distort SRT 0 \
          glitter_blue_tiled.gif

[IM 输出]
现在让我们把上面平铺后的闪粉放到“有洞”的图像下面,给巫师穿上新衣服。 |

  magick logo_holed.gif null: glitter_blue_tiled.gif \
          -compose DstOver -layers composite \
          -loop 0 -layers Optimize logo_glittered.gif

[IM 输出]
当然,你也可以把所有这些步骤放进同一个命令。这里我把挖洞限制在巫师斗篷上,它有两个分开的特定区域。 |

  magick logo.gif -alpha set -fuzz 10% -fill none \
          -draw 'matte 120,150 floodfill  matte 150,120 floodfill' \
          null: \( glitter_blue.gif -virtual-pixel tile \
            -set option:distort:viewport 300x300 -distort SRT 0 \) \
          -compose DstOver -layers composite \
          -loop 0 -layers Optimize logo_glitter_cloak.gif

[IM 输出]
上面的洞是用 Matte Fill 绘制原语创建的,它会从图像中选择一个实际点和颜色用于颜色替换。这意味着我不必像最初那样使用很高的模糊系数,因为比较颜色来自选中的具体区域。我还使用了更大的平铺“视口”,以确保完全覆盖被平铺的图像,而不需要知道它的精确尺寸。 | _使用通用扭曲运算符及其特殊的 "viewport' 选项(加入于 IM 6.3.6-0),也让你有机会用其他特殊方式修改扭曲图案。例如给它一种“透视”外观,或把图案旋转成非正交角度。这样可以增强平铺效果,使它看起来不那么均匀。

一些示例见仿射平铺。_
---|---

闪光 - 叠加几乎透明的闪粉

前面两种闪粉动画技术的主要问题是,它们属于全有或全无的替换。你不能使用图像原有的明暗或背景。闪粉也被完全限制在遮罩区域内,无法延伸到相关区域边界之外。因此,前面例子里巫师“帽子”这类小区域就不太适合用闪粉处理。闪光不同,因为添加的动画大多是透明的。因此原始图像仍能透出来。这类动画通常以两种方式之一添加到图像上。要么动画叠加层本身透明,要么采用黑色背景加白色“火花”的形式,在图像应变亮的地方进行增亮。

建设中

这里是一个几乎透明的“闪光”叠加示例。示例在此 如你所见,使用这种形式时可以得到彩色闪光叠加。它的主要问题是保存时使用了 GIF 动画(这通常会发生),所以叠加层有严重锯齿。也就是说,它不能包含任何半透明像素来平滑叠加图像的外观。如果包含了,最终结果中“闪光”周围会出现糟糕的黑色光晕。让我们把它遮罩并叠加到巫师上。示例在此 另一种闪光形式是在黑色背景上的白色闪光(灰度图像)。这些会被遮罩并叠加,用来增亮图像以添加闪光。例如... 示例在此 闪光最好的地方之一,是你可以生成一系列帧,让闪光慢慢出现然后消失。这可能会相当复杂,但并不难做。示例在此

添加耀斑和星芒动画

闪粉由单个亮点组成,闪光可以叠加到图像的某些区域,而耀斑通常是单独添加的。一个“耀斑”基本上是一个点,在一瞬间闪亮并覆盖很大区域。“星芒”类似,只是覆盖形式更像亮度“射线”。它们通常从特定点“播种”出来,但结果往往会至少在一瞬间远远扩展到播种区域之外。例如,把耀斑用遮罩限制在特定区域,看起来会非常非常愚蠢且不自然。耀斑更困难的方面在于找到好的“种子”点,并恰当地安排多个耀斑的时序。

建设中

我想创建的最终示例... 一个“闪光”沿着巫师的
魔杖向上移动,然后产生耀斑,并溶解成一片区域中的许多小闪光耀斑。
然后这个序列重复。

调整动画尺寸

调整动画尺寸的问题

调整 GIF 动画尺寸时最大的麻烦在于,"[-resize](https://imagemagick.org/command-line-options/#resize)" 运算符专门设计成让结果图像(调整尺寸后)尽可能接近理想状态。它通过合并并在图像中生成大量额外颜色来改善观感。得到的图像远不适合保存到受限的 GIF 文件格式中。由于 GIF 颜色表有限,这会在调整尺寸后的图像中造成严重的减色。对于单幅 GIF 图像,这还不算太糟;但对于 GIF 动画,减色后颜色集合的默认误差校正抖动会在帧之间产生“抖动噪声”,进而导致最终文件大小的帧优化很差。当同时使用透明色时情况更糟,而这在网页常用的典型 GIF 动画中非常常见。对于本来不需要透明度的动画,透明度也常用于压缩优化技术。实际发生的是,"[-resize](https://imagemagick.org/command-line-options/#resize)" 会在叠加图像中产生半透明像素。随后这些图像保存回 GIF 文件格式时,这些像素会被转换成完全透明或完全不透明,两者都会在结果动画中造成严重颜色失真。如果使用任何形式的优化... 帧、透明度或 LZW... 那么透明度效果基本上会导致一个灾难性调整尺寸的 GIF 动画。事实就是如此!所以你得接受它。即便你避免使用 "[-resize](https://imagemagick.org/command-line-options/#resize)",改用 "[-sample](https://imagemagick.org/command-line-options/#sample)",如果不先 "[-coalesce](https://imagemagick.org/command-line-options/#coalesce)" 动画,仍会遇到严重问题。

调整动画尺寸的技术

如上所示,调整 GIF 动画尺寸存在严重问题,而且没有一个容易解决。解决方案通常还取决于最初被调整尺寸的图像类型,是卡通类图像,还是现实世界的视频图像。下面是我知道的,或别人贡献的方法...

避免调整尺寸

如果可能,完全不要调整尺寸。例如,你可以用画布或视口裁剪把动画裁到适合所需空间的大小。或者一开始就生成正确尺寸的 GIF 动画。两种技术通常都不是最佳选择,但如果能做到,可以考虑,因为它会省去大量问题和抓头发的痛苦。

直接调整尺寸

如前所述,直接使用 "[-resize](https://imagemagick.org/command-line-options/#resize)" 会遇到问题,要么是每帧颜色数量的问题,要么是半透明颜色的问题。例如,这会变得非常糟糕... |

  magick script_k.gif -resize 20x20 script_k_direct.gif

[IM 输出]
[IM 输出]
这次效果并不好,原因是原始图像带有很重的帧优化。动画中的每个“帧”尺寸并不相同,而 "[-resize](https://imagemagick.org/command-line-options/#resize)" 会把每一帧图像与其他图像完全分开调整尺寸。也就是说,上面调整的是实际帧图像,而不是把动画的虚拟画布调整到给定尺寸。实际上,我还挺惊讶结果动画没有比展示出的空白区域更“疯狂”。这引出了调整动画尺寸的第一点。首先确保所有帧都已完整定义,并且所有优化都已移除。换句话说,在尝试调整尺寸之前先合并 (Coalesce)动画。例如... |

  magick script_k.gif -coalesce  -resize 20x20  script_k_direct2.gif

[IM 输出]
[IM 输出]
下一个问题是透明颜色。如果你查看上面的结果,会看到较小动画的边缘有严重锯齿(“阶梯状”)。这是因为 GIF 无法保存 "[-resize](https://imagemagick.org/command-line-options/#resize)" 运算符生成的半透明颜色。动画对象内部的颜色也会被合并生成新颜色,但通常还远没有边缘锯齿那么糟糕。

通过扁平化调整尺寸,一个通用解法。

生成 GIF 缩略图时,最好的想法是完全避开透明度问题。也就是在调整动画尺寸之前或之后扁平化动画。这样就不会丢失调整尺寸后图像边缘的“抗锯齿”。事实上,我发现大多数好的 GIF 动画网站在生成 GIF 动画缩略图时正是这样做的。当然,缩略图随后会被限制为只能用于特定颜色背景,通常是“白色”,有时是“黑色”或“银色”(网页灰),不过最后这种现在不太常见。例如,这里我为本网页合适的背景色创建一个较小的缩略图。 |

  magick script_k.gif -coalesce \
          -bordercolor LightSteelBlue -border 0 \
          -resize 20x20  -layers Optimize   script_k_thumbnail.gif

[IM 输出]
[IM 输出]
这是通用 GIF 缩略图处理的推荐解决方案。任何其他方法都需要人工控制,或非常复杂的 GIF 缩略图处理逻辑。

颜色表爆炸

最大的问题(正如我在本节开头提到的)是图像中会生成大量额外颜色,尤其在线条附近以及相邻颜色区域的边缘处。图像边缘周围还会出现由半透明颜色组成的调整尺寸光晕。这反过来会增大简单少色动画所需颜色表的大小,进而意味着保存一个调整尺寸后的简单动画时文件更大。更糟的是,调整尺寸后的动画中每一帧很可能都会生成一组不同颜色,进一步增大“缩略图化”动画的文件大小。此外,在颜色量化之后,你可能不再拥有与原动画相同的特定颜色(见量化不会保留颜色)。也就是说,原来一个纯白的简单区域,现在可能变成偏白区域。

使用 Sample 调整尺寸

调整尺寸时避免生成额外颜色的最简单方法,是对动画使用 "[-sample](https://imagemagick.org/command-line-options/#sample)",而不是 resize。这会保留动画中的当前颜色,并让你能轻松在新尺寸上重新优化动画。 |

  magick script_k.gif -coalesce  -sample 20x20  script_k_sample.gif

[IM 输出]
[IM 输出]
不过,虽然这能工作,但你基本上是在从图像中移除若干行和列像素,因此过程中会丢失图像数据和质量。对于卡通类图像,这常会留下“点状”边框,并丢失或扭曲细节。如果把动画缩小到原始尺寸的一半以下,如上例所示,结果通常会相当糟糕,尤其是在动画中使用纹理或其他颜色图案时。难怪许多 GIF 动画库里充满了这类从网上各处复制来的糟糕 sample 调整尺寸动画。我经常希望他们清理掉这类垃圾,但那意味着可提供的 GIF 数量会减少,进而减少可用 GIF 数量的营销统计,而广告部门并不喜欢这样。结果就是,糟糕的 GIF 动画很常见。

使用 Liquid Resize 调整尺寸

与上面使用 Sample 方法类似的一种方法是使用 Liquid Rescale,也称为 Seam Carving。它同样会从相关图像中移除或添加整列/整行像素,但会尝试以尽可能保留图像复杂度的方式进行。请查看上面的链接,了解如何用它生成更好的调整尺寸图像。遗憾的是,目前还没有办法把它用于通用动画图像,因为它并不了解图像复杂度,我们也无法提取这种缩放方法,以一致方式应用到动画的每一帧。希望未来某个时候这会改变。

调整尺寸并恢复颜色

对动画进行采样只会移除行和列像素,并可能移除细线和其他重要细节。但使用 "[-resize](https://imagemagick.org/command-line-options/#resize)" 合并像素,又会为 GIF 格式生成过多新颜色。因此显而易见的解决方案是先执行 "[-resize](https://imagemagick.org/command-line-options/#resize)",然后使用颜色映射表,用原动画的颜色来恢复调整尺寸后动画的颜色。

FUTURE: example with original color table restored

这还有不生成局部颜色表的额外好处。不过,如果关闭抖动以避免任何“抖动噪声”,结果可能更好。对于具有大块平滑着色区域的卡通类图像尤其如此。

FUTURE: non dithered color table restored example

完整颜色优化

如果你不喜欢糟糕的 sample 缩略图,那就要面对对调整尺寸后的 GIF 动画进行完整颜色优化的前景。也就是整理出你希望动画保留哪些“新”颜色。不过对大多数动画来说,这通常并不太糟,但对于更复杂的动画,例如把视频转换为 GIF 动画,可能会是一项很大的工作。也就是说,如果你处理的是卡通类动画,现在就会有严重抗锯齿的线条和边缘。对于涉及透明背景的动画,你还必须正确处理动画边缘周围的半透明像素,这些同样由 resize 的抗锯齿特性造成。可用于处理这一点的多种方法,见 GIF 布尔透明度一节。

大幅缩小尺寸

当你计划把一个大动画调整为小得多的动画时,会遇到动画重要部分消失的问题。这其实对静态图像和动画都是问题。相关已知解决方案见调整线稿尺寸
欢迎提供任何进一步的建议、想法或技术。


合并多个动画

我之前说过,但在合并动画时它会变得尤其重要...

尽可能多地了解你正在处理的动画!

"[gif2anim](../static/img/scripts/gif2anim)" 脚本非常适合这个目的。它的姊妹脚本 "[anim2gif](../static/img/scripts/anim2gif)" 也常在这里用于使用原始设置重新创建动画。(脚本的基本用法见动画列表信息。)如果不了解动画的工作方式,几乎不可能以各种方式合并它们。可以开发程序(这些示例的最终目标)来做这件事。但这类程序通常非常复杂,并可能产生意外结果。因此你仍应跟随这些示例,因为它们会让你深入理解动画应如何处理和合并。

串行或按时间追加

把两个 GIF 动画按时间顺序追加在一起,让一个序列接在另一个后面,对 IM 来说很简单。你基本上只需要把它们列在命令行上,它们就会彼此接续。但这可能并不像看起来那么容易。例如,在网上搜索了一番之后,我找到(好吧,是偷来并为了本示例大幅修改)了一对字母被书写的动画。现在我想把这些图像接起来,让一个动画完成后下一个开始,就好像有人正在写单词 'OK'。下面是这些字母、“动画序列”,以及这两个动画内部的详细信息。 | |

  gif2anim -n script_o.gif
  gif2anim -n script_k.gif

| [IM 文本]

| | [IM 文本]

[IM 输出]
[IM 输出]
这些序列从一个空画布开始,然后缓慢地向这个画布添加和修改像素。它们从不移除、清除或让前面帧添加的任何像素变透明。不过对我们的目的来说,它们是否这样做并不重要,因为对结果影响很小。动画中的帧数也不会影响这个操作。真正重要的是帧时序,因为它可能造成问题。尤其注意第一帧,或者在这个例子中是最后一帧上的时间延迟。这种技术很常见,用来在动画清空并重新开始之前,让观看者有时间看到最终结果。正是这些延迟和帧,会在按时间追加时给我们带来问题。还要注意,'k' 动画在动画序列中间有一个小延迟。这个延迟代表该动画中第一笔结束和第二笔开始。这个延迟也需要保留,意味着我们不能简单地把动画中的所有时间序列改成一个常量。上面没有显示的一点是,两个动画的第一帧其实都是空白画布。第二个动画中的这个画布大概应该被丢弃,因为它只是无用的时间浪费;但第一个动画中的它应被保留,作为起始延迟。现在我们已经检查了两个动画,来试着把它们接起来,让一个在时间上接在另一个之后。按时间追加动画实际上是一个非常简单的操作,只需在命令行上追加两个动画图像。那就试一下... |

    magick script_o.gif script_k.gif   script_ok_try1.gif

[IM 输出]
结果远不完美。字母按正确顺序绘制,但彼此叠在一起!不仅如此,由于第一个 'o' 动画比第二个 'k' 动画(53 像素)更窄(40 像素),最终 'k' 字母的最后一小段被较小的框架画布尺寸裁掉了。第二个动画的位置可以用上面展示的相对 repage移动。这种重新定位方法会保留该动画中可能存在的任何已有偏移,只是把它们作为一个相关整体一起移动。在这个例子中,几乎所有帧都有已有偏移,因为这是一个高度优化的动画。为了容纳这个移动后的位置并避免第二个动画被“裁切”,我们还需要扩大整个动画的画布尺寸。在读取第一个动画或帧之前改变画布尺寸,会扩大动画运行的画布区域,防止“K”被裁切。 |

    magick -page 90x54 script_o.gif \
            \( script_k.gif -repage +37+0\! \)   script_ok_try2.gif

[IM 输出]
结果有了很大改善。不过现在两个字母绘制之间的延迟明显可见。我们想要的是第一个 'O' 动画最后一帧的延迟小得多,只要足够看起来像那位隐形画师正在重新摆放笔尖即可。为此,我们复制第一个动画的最后一帧,然后使用 "[-set](https://imagemagick.org/command-line-options/#set)" 运算符只改变这一帧的延迟。接着通过删除原始未修改图像,把该帧重新加入图像序列。另外,由于我们已经在两个字母绘制之间设置了合适的延迟,第二个动画中的初始空白画布(只代表初始启动延迟)现在已经多余,因此可以直接删除该帧而不会有问题。如果这一帧实际上包含图像的一部分,那么我们可能需要调整它的延迟,而不是移除它。 |

    magick -page 90x54 script_o.gif \( +clone -set delay 20 \) -delete -2 \
            \( script_k.gif -delete 0 -repage +37+0\! \)     script_ok.gif

[IM 输出]
这样,我们对两个动画的串行或按时间追加就完成了,并修正了这两个特定动画相关的所有小问题。注意,我从未尝试全局改变所有单独帧或它们的时序延迟。也就是说,我在达成目标的同时,尽可能保留原动画。这很重要,因为并不是所有动画都在帧之间使用固定时序延迟,改变这一点可能会让动画看起来很糟。

并排追加(时间同步)

假设你希望两个动画并排追加,但同时让动画的两个部分一起播放。这并不容易,因为你需要把两个动画中的每一对帧追加(或合成)在一起,让动画也能一起工作。真正的问题在于,IM 命令行只能处理单一图像序列。它没有 API 那样的便利,可以保持两个独立图像序列,循环遍历并把它们追加成第三个序列。我能想到三种基本技术来做这种追加。不过在开始之前,你应先研究这两个动画,检查时间序列和动画的其他细节。"[gif2anim](../static/img/scripts/gif2anim)" 脚本很适合做这件事,生成的 ".anim' 文件稍后也会很有用。 | |

  gif2anim -n bag_left.gif
  gif2anim -n bag_right.gif

| [IM 文本]

| | [IM 文本]

[左]
[右]
如果你查看信息摘要,会看到两个动画具有完全相同的帧数,以及几乎完全相同的时间序列。这里重要的是时序相似性,你可以说这些动画已经“时间同步”。不过,虽然时序可能正确,动画却是帧优化后的,而不是完全合并后的。但画布区域高度相同,使得并排追加这两个帧是可行的。实际上,这个动画被很糟糕地“拆分”了(见下一组示例中的拆分动画),导致“猫”动画被切成两半,原始版本丢失。其他修改造成了非常轻微的时序差异,只是让这种分割更加明显。这是 gmabrysIM 论坛讨论中向我提出的问题,虽然他给出的实际问题要糟糕得多得多。[左][右] 现在,浏览器通常会分别播放每个独立 GIF 图像,并不做任何同步。因此两个动画可能彼此不同步,产生一只看起来像经历过电锯惨案的“猫”。你也许能在右边看到这种效果,我把两个动画并排放在浏览器页面上,尤其当你通过慢速链路访问远端服务器时更明显。现在让我们尝试把它们追加成一个正确同步的动画图像。

追加单独文件

最简单的方法是把两个动画合并,并把它们分离成单独的图像文件,每帧一幅图像。然后可以按需要把这些单独图像追加在一起(或以其他方式修改帧)。完成后,新的帧可以用于重建动画。不过这要求你保存大量关于动画的额外信息,而这些信息在此处理过程中很容易丢失。 |

  # Separate animations into coalesced frames (plus a ".anim" file)
  gif2anim -c bag_left.gif
  gif2anim -c bag_right.gif

  # Append the separated frames them together
  for i in `seq -f '%03g' 1 11`; do \
    magick bag_left_$i.gif bag_right_$i.gif +append bag_append_$i.gif; \
  done

  # Rebuild the animation (using one of the ".anim" files)
  anim2gif -c -b bag_append  bag_left.anim

  # Cleanup
  rm -f bag_left.anim bag_right.anim
  rm -f bag_{left,right,append}_???.gif

[IM 输出]
如你所见,这是一个相当复杂的过程,会生成许多单独的临时图像,因此完成后需要不少清理。当然,如果你在调试上面的流程,这些单独临时文件会让你更容易弄清处理哪里出了问题。它还展示了 "[gif2anim](../static/img/scripts/gif2anim)" 脚本及其反向 "[anim2gif](../static/img/scripts/anim2gif)" 脚本在分离、保存动画元数据,以及稍后重建 GIF 动画方面的能力。基本上,它让你可以保留动画的原始时序,而不需要把这些时序直接写进脚本。最终图像仍需要重新优化,不过在这个例子中,由于每一帧之间都有很多内容同时变化,你能得到的优化会非常少。

图层合成

更好的技术是使用多图像列表图层合成来叠加动画。这只涉及扩大其中一组图像,并叠加另一组图像来把它们接在一起。事实上,这正是普通 "[-append](https://imagemagick.org/command-line-options/#append)" 运算符内部所做的事,所以差别并不大。这里我只是告诉 IM 画布要做多大,并用 "[-coalesce](https://imagemagick.org/command-line-options/#coalesce)" 把它填满。随后我以适当偏移叠加另一个合并后的动画。 |

  magick bag_left.gif -repage 97x92 -coalesce \
          null: \( bag_right.gif -coalesce \) \
          -geometry +50+0 -layers Composite    bag_append.gif

[IM 输出]
当然,上面的技术意味着我需要知道最终动画到底多大,以及叠加动画所需的偏移。但这个过程很快,效果也很好,脚本化命令可以预先读取图像来确定这些信息。要做一个更通用的动画追加方法,我们需要做一些更精巧的图像处理,以自动确定追加后的最终尺寸和偏移。如果不预先读取动画,要做到这一点需要绕一些圈子,不过单命令的通用动画追加是可行的。首先,我们需要追加每个动画的第一帧合并图像,以创建一个大小正确的画布,然后清空它。第一个动画被合并并叠加到这个画布的左半部分;随后第二个动画被合并,并用 "-gravity East" 叠加到预先准备好的画布最右半部分,以避免需要偏移。 |

  magick bag_left.gif'[0]' -coalesce \( bag_right.gif'[0]' -coalesce \) \
          +append -channel A -evaluate set 0 +channel \
          bag_left.gif -coalesce -delete 0 \
          null: \( bag_right.gif -coalesce \) \
          -gravity East  -layers Composite    bag.gif

[IM 输出]
这样,你就有了一种把两个时间同步动画追加在一起的通用技术。

双重追加、追加 - 或追加动画字体

在结束动画追加之前,我还想展示另一种技术。这个技术可以同时追加多个动画,但代价是会丢失原本存在的所有时序信息。很多时候(但并非总是)这些时序并不是很大的损失。基本上,我们把每个动画的所有帧垂直追加成一幅单图像,然后再把整个动画作为两幅简单图像追加或叠加起来。这有点像把两条胶片带并排贴在一起,生成更宽的胶片带。 |

  magick \( bag_left.gif  -coalesce -append \) \
          \( bag_right.gif -coalesce -append \) \
          +append  -crop x92 +repage \
          -set delay 30     bag_dbl_append.gif

[IM 输出]
这不需要任何临时文件,但如我开头所说,所有原始时间延迟都丢失了。在这个例子中,我只是把所有动画延迟设为一个常量,得到一个合理但不同的结果。另外,为了重建动画,我们需要知道原动画的帧高度,以便正确分割(平铺裁剪)变宽后的“胶片带”。虽然可以使用 "[gif2anim](../static/img/scripts/gif2anim)" 脚本恢复这些时序,但这样做有点违背使用这种方法的目的,你还不如直接使用第一种动画追加技术,把单独帧作为临时文件追加。由于你是把动画作为简单图像来追加,所以可以一次性把一整组动画全部追加在一起(生成更宽的“胶片带”),这正是这种技术很有用的原因。例如,你可以把它用于使用相同时序的动画字体。不过我发现,虽然许多动画字体具有相同帧数,但它们通常为每个字母设置略有不同的时序,以便让动画字母不同步(为何这样做更好,见拆分动画)。另一方面,霓虹灯标志应该具有同步的动画时序,所以我用它作为示例...

  magick \( neon_h.gif -coalesce -append \) \
          \( neon_e.gif -coalesce -append \) \
          \( neon_l.gif -coalesce -append \) \
          \( neon_l.gif -coalesce -append \) \
          \( neon_o.gif -coalesce -append \) \
          +append  -crop x60 +repage  -set delay 100  neon_hello.gif

[IM 输出] [IM 输出] [IM 输出] [IM 输出] [IM 输出] [IM 输出]

你也可以做得更花哨一些,调整结果动画的时序和循环次数。

  magick neon_h.gif'[0]' neon_e.gif'[0]' neon_l.gif'[0]' neon_l.gif'[0]' \
          +append \( +clone \) -append \
          \( neon_o.gif -coalesce -append \)    +append \
          \( +clone \) -append \( +clone \) -append \( +clone \) -append \
          -crop x60 +repage   -set delay 3 \
          \( -clone 0  -set delay 300 \) -swap 0,-1 +delete \
          \( -clone 1  -set delay  10 \) -swap 1,-1 +delete \
          \( +clone    -set delay 200 \) +swap      +delete \
          -quiet -layers OptimizeFrame   neon_hell.gif

[IM 输出]

前两行生成标志中“始终点亮”的部分(之前动画字母的第一帧)。之后添加最后一个“坏掉”的字母,并把整个动画加倍几次,生成大约 16 帧。时序被设置为完成期望效果:第一帧和最后一帧显示很长时间,其余帧则非常快速地闪过("-delay 10")。实际上,这个 GIF 动画的优化后尺寸比你根据涉及帧数可能想到的要小得多。基本上,IM GIF 优化器发现它只需要每隔一帧重新叠加“O”动画,并使用 'Previous' 处置来恢复前一个点亮的“O”。因此,这个动画只比基本闪烁的未优化“hello”图像大约 50%。你可以自己检查一下。你能改进这个霓虹动画吗?让它更真实吗?GIF 动画没有声音真可惜。

拆分动画

现在动画已经重新接在一起,让我们尝试把它正确拆分,以便在 Web 服务器上使用,使各个部分可以分别动画而不互相干扰。这其实相当困难,我不会尝试把这个过程完全自动化。不过 WWW 上确实有工具能做到这一点。首先,我们需要研究动画,找出整个时段内动画的哪些部分发生变化。为此,我们需要找出从一帧到下一帧的差异,把它们全部加在一起,生成一张地图,显示哪些区域正在动画,哪些区域始终完全静止。这很棘手。基本上,要使用多图像 Alpha 合成找出动画中每一帧之间的 '[Difference](compose.html#difference)' 图像。这些灰度差异图像会被相加,然后分离通道并再次相加。最终的阈值处理会把动画任意两帧之间任何非零变化变成纯白。结果是一幅黑色图像,任何发生变化的位置都是白色,从而突出变化区域。

  magick   bag.gif   -coalesce  -set delay 0 \
          -bordercolor red -border 0 -alpha off    null: \
          -duplicate 1,1--2 -compose difference -layers composite \
          +delete -compose plus -background black -flatten \
          -separate -flatten -threshold 0 bag_areas.gif

[IM 输出] [IM 输出]

现在我们可以看到,这个动画至少可以分成三个区域:顶部的“猫”区域、左边的小“熊”,以及右边拍动的“翅膀”。所有切割都是简单的正交(垂直或水平)切割。那么就用几个简单的动画视口裁剪来这样做。 |

  magick bag.gif -coalesce  -crop 97x39+0+0\!   bag_cat.gif
  magick bag.gif -coalesce  -crop 50x54+0+39\!  bag_bear.gif
  magick bag.gif -coalesce  -crop 47x54+50+39\! bag_wing.gif

| [IM 输出]

[IM 输出] | [IM 输出]
这三幅图像可以由浏览器一起显示,并且不会呈现“德州电锯惨案”的效果,因为任何时候都没有一个子动画跨越另一个子动画的边界。现在从技术上说,你还可以再切几刀,把非动画区域与动画区域分开,把这个动画拆成大约六个或更多区域,但这样做在优化上不会获得太多好处。它真正会做的只是让网页更复杂,并创建更多文件供用户下载。与较大的动画不同,这些较小区域会彼此相当独立地动画。我们甚至可以改变这些简单子动画的时序,而不会对结果产生不良影响,从而让它们与其他子动画完全不同步。结果是一个更好、更不重复的动画图像(见下方)。如果你研究“跳动的熊”和“拍动的翅膀”,会发现它们形成了一个简单的两帧循环,只是重复若干次以匹配挥手猫的时序。因此我们可以丢弃多余重复,简化这些动画。另外,“猫”的前两帧也完全相同。不过不同于“熊”和“翅膀”,你不能简单移除其中一个,因为每一帧都包含时间延迟,用来让“熊”和“翅膀”在没有猫出现时继续动画。要正确移除这些重复帧,需要使用 "[-layer](https://imagemagick.org/command-line-options/#layers)" 方法 '[RemoveDups](anim_opt.html#removeDups)',在合并动画中定位这类重复帧,并合并它们的时序。下面是三个分离动画的最终优化,并带有改善子动画整体不同步效果的时序修改。我还把三个动画按它们应显示的方式并排显示在页面上。 |

  magick bag_cat.gif -layers RemoveDups \
                           -quiet  -layers Optimize  bag_cat_opt.gif
  magick bag_bear.gif -delete 2--1 -set delay 47 \
                                   -layers Optimize  bag_bear_opt.gif
  magick bag_wing.gif -delete 2--1 -set delay 33 \
                                   -layers Optimize  bag_wing_opt.gif

| [IM 输出]

[IM 输出] | [IM 输出]
最后总结:两个原始(糟糕拆分的)图像合计 [IM 文本] 字节,约等于追加后的版本。正确拆分动画后,可以很好地优化子动画,三幅图像合计为 [IM 文本] 字节。节省得相当不错。

远距离变化帧拆分

建设中

示例:在不涉及透明度(固定背景)的情况下,拆分“两个相距很远的变化对象”的帧更新,但保留各部分之间的时序同步。然后用透明背景重复一次(需要 'OptimizePlus' 来生成“清除”的像素)。通用示例见拆分帧动作

合并时间不一致的动画

在两个动画能够合并为同步运行之前,你需要让所有动画使用相同数量的帧,并使用相同的一组时间延迟。合并有多困难,实际上取决于动画时序有多不一致。如果时间延迟基本恒定,你可以直接忽略它们,稍后修正时序。在一个 IM 论坛讨论中给出了一个可以在合并 2 帧动画和 6 帧动画时忽略时间的例子。另外,如果总循环时间差异很大,可能需要调整,使其中一个动画循环 2 或 3 次,以填满另一个动画的循环时间。基本上,时序才是关键。

大概会是这样的... * + 计算并调整动画,使其具有共同的总循环周期 * + 合并两个动画以移除任何帧优化。 * * 把帧时间延迟转换为动画开始后的经过时间。 * * 按需要加倍帧,以实现时间同步。 * * 把开始后的经过时间转换回帧时间延迟。 * + 按需要叠加合并后的时间同步帧。 * + 最优合并,并移除任何“零延迟”帧。 * + 重新优化新动画。

'*' 部分可以做成一个新的 "[-layer](https://imagemagick.org/command-line-options/#layers)" 方法,用来时间同步两个具有相近总循环时长的动画。

建设中

示例:时间不一致,但循环时间相同...

例如,假设你有两个三帧动画,时间延迟分别为
    10  10  10
    5    5  20

两个动画都已经是 30 个时间单位长,所以这不是问题。

现在把上面的内容转换成每一帧应出现的时间索引...
并显示各帧出现的整体时间线...
   0        10    20   |__ 注意,两个动画都在 30
   0   5    10         |   结束或循环

由此可见,你需要插入一些额外帧来让它们匹配。
第一个动画的第一帧需要在时间索引 5
重复

0->5  10  20
0  5  10

第二个动画的最后一帧也需要在
时间索引 20 被复制

0->5  10  20
0  5  10->20

上面的箭头 '->' 表示同一帧只是重复(复制)
到下一个时间索引中。它们实际上是同一幅图像。

既然两个动画中各帧的时序已经相同,你就可以
简单地合并(合成)这些帧,得到一个
4 帧长的最终动画。

这四帧将具有如下时间延迟
5  5  10  10
它们仍然相加为 30 个时间单位(每次循环的总时间)

当前开发状态....

IM 可以帮助收集时间延迟信息(试试 "gif2anim" 的 '-t' 选项)
并构建动画。但 IM 无法执行两个独立合并动画
所需的时间同步。这以后可能会成为一个特殊的
内置选项。

也就是说,你需要自己找出并加倍适当的合并动画帧,
从而把两个时间不一致的动画改造成两个
时间同步的动画。

一旦动画完成时间同步,就可以直接使用新的
"-layers Composite" 方法,非常轻松地叠加或合并
两个时间同步的动画。

不过,上面所有内容都假定两个动画的总循环时间
至少大致相等,或者并不是主要问题。


**简化解决方案**

一个简化的有限解决方案已在 [IM 论坛中讨论](https://magick.imagemagick.org/viewtopic.php?p=50325),用于快速变化的动画(表情)。

该解决方案会取每个动画并扩展它,使动画具有
固定帧率。也就是说,所有帧都会被复制,使每一帧
显示固定的 6 厘秒。因此,一个带 22cs
延迟的帧可能会被替换为 4 x 6cs 帧(总计 24cs)。

之后,动画会进一步修改,使较短动画
多次循环,最终让两个动画长度相同。
也就是说,让两个动画在时间和帧数两方面
都具有相同的整体长度。

一旦两个动画具有相同帧率和相同长度,就可以使用[图层合成](#composite)来合并/叠加这两个
动画,并放在正确位置。

随后可以使用[移除重复帧](anim_opt.html#removedups)优化结果,以移除多余帧,并
在保存前应用适当的时序调整和其他[优化](anim_opt.html#intro)。

把所有组成动画都保持为固定帧长形式的这种方法,
尤其适合动画库。


-----
其他待创建示例....
  * 把两个移动的等时动画叠加到单个动画中
    (跳舞的蝴蝶、绕行的原子,或者鸟?)
    这应该是直接的图层合成。

  * 把移动动画叠加到固定背景上。
    (随时间线性移动动画)

  * 叠加两个帧数不同但时间延迟恒定的动画
    (见 [IM 论坛讨论](https://magick.imagemagick.org/viewtopic.php?t=12573))。

  * 叠加两个时间不一致的动画(如上所述)

  * 把一个简单动画人物叠加到动画背景上。
    (完整动画合并)