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

ImageMagick 示例 -- 多图像图层

ImageMagick 示例前言与索引
图层简介
连接图像 (-append)

图像图层叠加简介

正如前面提到的,ImageMagick 处理的不只是单个图像,而是图像序列或图像列表。这让你可以把 IM 用在两种很特别的图像处理技术中。例如,你可以把列表中的每个图像看作时间上的一帧,于是整个列表就可以视为一个_动画_。这一点会在其他 IM 示例页面中讨论。参见动画基础。或者,你也可以把序列中的每个图像看作一组可透视投影片的_图层_。也就是说,每个图像都表示最终图像的一小部分。例如:第一个(最底层)图层可以表示背景图像。在它之上可以有一个模糊、半透明的阴影。接下来的图层图像包含投下该阴影的对象。再往上,则是写在该对象之上的一些文本图层。也就是说,你可以拥有一串图像或“图层”,其中每一层都为更复杂的图像再添加一个组成部分。每个图像图层都可以完全独立于其他图层进行移动、编辑或修改,甚至可以保存为多图像文件(例如 TIFF:、MIFF: 或 XCF:),或保存为单独图像,以便以后继续处理。这正是图像图层叠加的意义所在。只有当所有图像图层都创建完成后,才会通过 FlattenMosaicMerge 将所有图层化图像合成为一张最终图像。


连接图像

在用于处理多图像的操作中,连接(Appending)大概是最简单的一种。它基本上会把当前内存中的图像序列无缝拼成一列或一行。“[-append](https://imagemagick.org/command-line-options/#append)”选项会垂直连接,而加号形式的“[+append](https://imagemagick.org/command-line-options/#append)”会水平连接。例如,这里我们把一组字母图像并排连接起来,形成一个花哨的单词,这有点像“字体”中的各个“字形”或字母被组合在一起。

  magick font_A.gif font_P.gif font_P.gif font_E.gif font_N.gif \
          font_D.gif font_E.gif font_D.gif +append  append_row.gif

[IM 输出]

上面的例子(以非常基础的方式)类似于字体的处理方式。与真正的字体不同,你并不局限于两种颜色,而可以从单个字符图像生成非常花哨、色彩丰富的字母表。许多这类“图像字体”可以从 WWW 下载。一个很小的集合可以在 Anthony's Icon Library 中的 Fonts for Text and Counters 找到,上面使用的 Blue Bubble Font 也是我在那里找到的。还要注意,“[+append](https://imagemagick.org/command-line-options/#append)”运算符是在所有想要连接的图像都加入当前图像序列之后,作为最后一步执行的。例如,这非常适合给图像连接一个标签…… |

  magick rose: -background LawnGreen label:Rose \
          -background white  -append append_label.jpg

[IM 输出]
注意,“[-background](https://imagemagick.org/command-line-options/#background)”颜色用于填充任何未被图像占满的空间。当然,如果所有图像宽度相同,就不会留下这种可填充的空间。从 IM v6.4.7-1 起,可以使用“[-gravity](https://imagemagick.org/command-line-options/#gravity)”设置来指定图像应如何组合。因此在垂直连接时,设置为“Center”会让图像相对于最终结果图像居中(设置为“North”或“South”也会如此)。 |

  magick rose: -background LawnGreen label:Rose \
          -background white -gravity center -append \
          append_center.jpg

[IM 输出]
自然,任何“East”gravity 设置都会把图像对齐到右侧。 |

  magick rose: -background LawnGreen label:Rose \
          -background white -gravity east -append \
          append_east.jpg

[IM 输出]
使用“[+append](https://imagemagick.org/command-line-options/#append)”时也可以实现类似的垂直对齐。 | _在 IM v6.4.7 以前,要对齐被连接的图像要困难得多,右对齐通常需要使用“[-flop](https://imagemagick.org/command-line-options/#flop)”。或者,为了居中对齐连接,需要使用“[-extent](https://imagemagick.org/command-line-options/#extent)”或“[-border](https://imagemagick.org/command-line-options/#border)”来调整图像宽度。

例如,下面这个命令可以在较旧的 IM 6.3.2 版本中工作……_ | |

  magick rose: -background SkyBlue label:Rose \
          -background White -gravity center -extent 200x \
          -append -trim +repage   append_center_old.jpg

[IM 输出]
你也可以在同一个命令中使用多次连接操作,而不会对操作结果产生冲突或混淆(这在 IM v6 以前并非如此)。 |

  magick font_{0,0,6,1,2}.gif +append  dragon_long.gif \
          -background none   -append   append_multi.gif

[IM 输出]
我们先把每一行图像连接在一起,然后在其下方连接一个更大的图像。这非常简单直接。通过使用括号,可以只在较大图像之后连接数字。例如,这里先把所有数字连接在一起,然后再把它们垂直连接到我们在数字之前读入的龙图像上。 |

  magick dragon_long.gif  '(' font_{0,0,6,2,9}.gif +append ')' \
          -background none   -append   append_parenthesis.gif

[IM 输出]
| 在 UNIX shell 中使用上面的括号时,必须用引号包住,或用反斜杠('\')转义;否则 shell 会把它们解释成完全不同的东西。
---|---
| 由于这里只涉及两张图像,我们也可以不用括号,而直接使用“[+swap](https://imagemagick.org/command-line-options/#swap)”或“[-reverse](https://imagemagick.org/command-line-options/#reverse)”。
---|---

连接一个图像数组

你可以进一步创建一整个图像数组,并按行或按列构建它们。 |

  magick \( font_1.gif font_2.gif font_3.gif +append \) \
          \( font_4.gif font_5.gif font_6.gif +append \) \
          \( font_7.gif font_8.gif font_9.gif +append \) \
          \( -size 32x32 xc:none  font_0.gif +append \) \
          -background none -append   append_array.gif

[IM 输出]
严格说,第一组括号并不是必需的,因为此时还没有读入任何图像;但这样会让整个命令看起来更统一,也清楚表明它是在构建一个图像数组。另见 Montage 拼接模式,它提供了另一种创建等尺寸图像数组的方法。 | _“[-append](https://imagemagick.org/command-line-options/#append)”运算符只会连接实际图像,不会使用虚拟画布(图像 page)尺寸,也不会使用图像偏移。不过,虚拟画布信息似乎会被留在一种奇怪的状态:画布尺寸被相加,而偏移被设为某个未定义的值。

这可以视为一个 bug,也意味着在保存之前,或在把图像用于这些信息可能变得重要的操作之前,应当使用“[+repage](https://imagemagick.org/command-line-options/#repage)”重置输入图像或结果图像的虚拟画布。

这种情况很可能会在该操作未来的某次扩展中得到修复。因此建议谨慎使用,尤其是在重新连接平铺裁剪图像时。_
---|---

带重叠的连接

在 IM Forum 上,有用户询问一种简单的方法来连接带有一些重叠的图像。大家给出了许多方案。下面是其中最简单的方案之一,重叠量只在一个地方给出。 |

  magick granite: rose: -gravity east -background none \
          \( -clone 1 -chop 30x0 \) \( -clone 0,2 +append \) \
          -delete 0,2 +swap -composite append_overlap.gif

[IM 输出]
上面的做法不需要任何图像定位计算;那类计算通常涉及图像尺寸,并用于更通用的方案。参见下文的处理图像图层。这里的做法是在把结果连接到第一张图像之前,先切掉会重叠的部分,从而得到最终图像尺寸。然后再把原图像(按 gravity)合成到上面,产生实际的重叠。它也可以相当容易地改成垂直重叠,甚至从右到左重叠。

Smushing 连接

连接图像的另一种方式是 smushing。“[-smush](https://imagemagick.org/command-line-options/#smush)”运算符的工作方式很像上面的连接运算符,但它会接受一个参数,用来指定图像之间需要多少空间(或负空间)。例如,我们用它把前面的示例写得更简单一些。 |

  magick granite: rose: -background none -gravity Center \
          +smush -20 smush_overlap.png

[IM 输出]
这效果很好,不过这并不是该运算符真正的设计目的,而且它可能慢得多。smush 真正要做的是把“有形状的图像”移动到尽可能靠近的位置。例如,这里我生成字母“A”和“V”,并把它们“smush”到一起,使两者之间的空隙尽可能小。 |

  magick -background none -pointsize 72 \
          -fill red label:A -fill blue label:V \
          +smush 0 smush_append.png

[IM 输出]
注意,这两个字母连接得比 append 通常做到的要近得多,因为它利用了图像“形状”中的空白区域。上图中的缝隙来自两个字母边缘像素的抗锯齿。也就是说,这正是“[-smush](https://imagemagick.org/command-line-options/#smush)”被设计来做的事,不过它需要大量计算,因此比上面的 Append 慢很多。该参数是最终位置的偏移量,通常为正值以生成间隔,也可以为负值以产生重叠。 |

  magick -background none -pointsize 72 \
          -fill red label:A -fill blue label:V \
          +smush -15 smush_offset.png

[IM 输出]
如果使用非常大的负值,图像可能会以未文档化的方式被裁剪。


多个图像对的合成

合成是用于把两个单独图像合并在一起的底层操作。几乎所有图层技术最终都会退化为一次合并两个图像,直到只剩下一张图像。因此,让我们先看看对图像对进行底层合成的几种方法。

使用 Composite 命令

使用 ImageMagick 将两张图像组合在一起的传统方法,是通过“magick composite”命令。该命令一次只能组合两张图像,并把每次操作的结果保存到一个文件中。当然,这并不妨碍你一次处理一张图像,用它来叠加多个图像…… |

  magick -size 100x100 xc:skyblue composite.gif
  magick composite -geometry  +5+10 balloon.gif composite.gif composite.gif
  magick composite -geometry +35+30 medical.gif composite.gif composite.gif
  magick composite -geometry +62+50 present.gif composite.gif composite.gif
  magick composite -geometry +10+55 shading.gif composite.gif composite.gif

[IM 输出]
| _由于所有输入图像都会在输出图像打开之前由 ImageMagick 读入,因此你可以输出到其中一个输入图像。这允许你像上面那样反复处理同一个图像,而不会出问题。

不要对 “JPEG” 这类有损图像格式这样做,因为格式误差会不断累积,基础图像会很快劣化。_
---|---
你还可以使用“[-geometry](https://imagemagick.org/command-line-options/#geometry)”设置来调整叠加图像的尺寸并定位它。 |

  magick -size 100x100 xc:skyblue comp_resize.gif
  magick composite -geometry 40x40+5+10  balloon.gif comp_resize.gif comp_resize.gif
  magick composite -geometry      +35+30 medical.gif comp_resize.gif comp_resize.gif
  magick composite -geometry 24x24+62+50 present.gif comp_resize.gif comp_resize.gif
  magick composite -geometry 16x16+10+55 shading.gif comp_resize.gif comp_resize.gif

[IM 输出]
magick composite”命令还有其他一些优点:你可以使用“[-compose](https://imagemagick.org/command-line-options/#compose)”选项控制图像绘制到背景上的方式,而其相对位置会受“[-gravity](https://imagemagick.org/command-line-options/#gravity)”设置影响。你还可以对叠加图像使用“[-tile](https://imagemagick.org/command-line-options/#tile)”,让它刚好覆盖背景图像,而无需指定平铺范围。这是只有使用“magick composite”时才可用的功能。此方法最大的缺点是你要使用多个命令,IM 必须把工作图像写出到管道或磁盘,再让下一个命令重新读入。若要查看更多使用“magick composite”命令把图像叠加到其他图像上的示例,请参见“通过叠加图像进行标注”和“使用 Gravity 定位图像”。

Convert 的 Composite 运算符

[-composite](https://imagemagick.org/command-line-options/#composite)”运算符可在“magick”命令中使用。更多细节见 IM 中的图像合成。这让你可以完成与上面相同的事情,但全部放在一个命令中。 |

  magick -size 100x100 xc:skyblue \
          balloon.gif  -geometry  +5+10  -composite \
          medical.gif  -geometry +35+30  -composite \
          present.gif  -geometry +62+50  -composite \
          shading.gif  -geometry +10+55  -composite \
          compose.gif

[IM 输出]
这会先创建一个颜色为“skyblue”的画布图像,然后把后续每个图像按给定位置叠加到该画布上。这里的“[-geometry](https://imagemagick.org/command-line-options/#geometry)”是一个非常特殊的运算符:它不仅为下一次“[-composite](https://imagemagick.org/command-line-options/#composite)”操作设置叠加位置,还会“[-resize](https://imagemagick.org/command-line-options/#resize)”当前图像序列中的_最后一个_图像(且只会调整最后一个图像)。 |

  magick -size 100x100 xc:skyblue \
          balloon.gif  -geometry 40x40+5+10   -composite \
          medical.gif  -geometry      +35+30  -composite \
          present.gif  -geometry 24x24+62+50  -composite \
          shading.gif  -geometry 16x16+10+55  -composite \
          compose_geometry.gif

[IM 输出]
请注意,建议避免“[-geometry](https://imagemagick.org/command-line-options/#geometry)”这种“调整尺寸”副作用,哪怕它很方便。基本上,它更多是一个向后兼容效果,在某些情况下还可能产生其他影响。下面是更明确的推荐写法…… |

  magick -size 100x100 xc:skyblue \
          \( balloon.gif -resize 40x40 \) -geometry +5+10   -composite \
          \( medical.gif               \) -geometry +35+30  -composite \
          \( present.gif -resize 24x24 \) -geometry +62+50  -composite \
          \( shading.gif -resize 16x16 \) -geometry +10+55  -composite \
          compose_resize.gif

[IM 输出]

绘制多个图像

同样使用“magick”,你也可以使用绘制图元把图像叠加到它的工作画布上。 |

  magick -size 100x100 xc:skyblue \
          -draw "image over  5,10 0,0 'balloon.gif'" \
          -draw "image over 35,30 0,0 'medical.gif'" \
          -draw "image over 62,50 0,0 'present.gif'" \
          -draw "image over 10,55 0,0 'shading.gif'" \
          drawn.gif

[IM 输出]
当然,你也可以为叠加图像指定调整尺寸。 |

  magick -size 100x100 xc:skyblue \
          -draw "image over  5,10 40,40 'balloon.gif'" \
          -draw "image over 35,30  0,0  'medical.gif'" \
          -draw "image over 62,50 24,24 'present.gif'" \
          -draw "image over 10,55 16,16 'shading.gif'" \
          drawn_resize.gif

[IM 输出]
在叠加过程中,“drawn”图像也可以被旋转、缩放和仿射畸变。不过,要让它按你想要的方式工作可能会有些棘手。绘制出来的图像和文本一样,会受“[-gravity](https://imagemagick.org/command-line-options/#gravity)”影响。


多图像图层叠加

真正的图像图层叠加需要一种方法来把多个图像组合在一起,而不必逐对单独合成。这正是各种 [-layers](https://imagemagick.org/command-line-options/#layers) 运算符方法发挥作用的地方。图层图像的顺序可能很重要,因此最好理解特殊的图像序列或列表运算符。注意,“图层图像”的处理方式实际上与“动画帧”的处理几乎相同。因此,也建议你查看动画基础动画修改,了解处理单个“图层”或“帧”的技术。事实上,动画在处理图像时也经常使用同一个 [-layers](https://imagemagick.org/command-line-options/#layers) 运算符。

Flatten -- 放到背景图像上

[-layers](https://imagemagick.org/command-line-options/#layers) **flatten**”图像列表运算符(或其快捷形式“[-flatten](https://imagemagick.org/command-line-options/#flatten)”)基本上会把给定的每个图像“合成”到背景上,形成一张单独图像。不过,图像位置由它们当前的虚拟画布或 Page 偏移指定。例如,这里我创建一个漂亮的画布,并指定要叠加到该画布上的每个图像。 |

  magick -size 100x100 xc:skyblue \
          -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 \)  \
          -layers flatten  flatten_canvas.gif

[IM 输出]
| _从 IM v6.3.6-2 起,“[-flatten](https://imagemagick.org/command-line-options/#flatten)”运算符只是“[-layers](https://imagemagick.org/command-line-options/#layers) 'flatten'”方法的别名。

因此,“[-flatten](https://imagemagick.org/command-line-options/#flatten)”选项可以看作同名“[-layers](https://imagemagick.org/command-line-options/#layers)”方法的快捷方式。_
---|---
你不需要像上面那样创建初始画布,也可以让“[-flatten](https://imagemagick.org/command-line-options/#flatten)”替你创建一个。画布颜色将是当前的“[-background](https://imagemagick.org/command-line-options/#background)”颜色,而其尺寸由第一张图像的虚拟画布尺寸定义。 |

  magick \( -page 100x100+5+10  balloon.gif \)   \( -page +35+30 medical.gif \)  \
          \( --page +62+50        present.gif \)   \( -page +10+55 shading.gif \)  \
          -background dodgerblue  -layers flatten  flatten_page.gif

[IM 输出]
| _虽然“[-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 与 Page 偏移

如果需要配合“[-gravity](https://imagemagick.org/command-line-options/#gravity)”进行放置,请查看上面的多图像合成方法,或能同时处理两种定位方法的特殊图层合成方法。_
---|---
如果某个图像没有出现在定义好的虚拟画布区域内,它会按情况被裁剪或忽略。例如,这里我们使用了较小的画布尺寸,导致后面的图像不能完整出现在画布上。 |

  magick \( -page 75x75+5+10  balloon.gif \)   \( -page +35+30 medical.gif \)  \
          \( -page +62+50 present.gif \)   \( -page +10+55 shading.gif \)  \
          -background dodgerblue  -flatten  flatten_bounds.gif

[IM 输出]
Flatten 的常见用途是把多个图像“图层”合并在一起。也就是说,你可以生成较大图像的各个部分,通常使用括号把图像运算符限制在正在生成的单个“图层”图像上,然后把最终结果 flatten 到一起。一个典型用途是创建一个阴影图像图层,再把原始图像 flatten 到它上面。例如…… |

  magick balloon.gif \( +clone  -background navy  -shadow 80x3+5+5 \) +swap \
          -background none   -flatten   flatten_shadow.png

[IM 输出]
注意,因为我希望阴影位于原始图像下方,所以需要交换这两张图像,把它们放到正确顺序。 | _不建议使用Flatten 来添加生成的阴影图像,因为生成的阴影图像可能带有负图像偏移。

推荐的解决方案,正如阴影图像一节中给出的,是使用更高级的图层合并技术,后面会讨论。_
---|---
由于虚拟画布只包含一个尺寸,结果图像将会是这个尺寸,但没有虚拟画布偏移;因此你无需担心最终图像中存在任何偏移。用虚拟画布来定义叠加图像所在的画布,也意味着你可以用它给图像添加周围边框。例如,这里我设置图像的尺寸和虚拟偏移,用来把一张图像“填充”到指定尺寸。 |

  magick medical.gif -set page 64x64+20+20 \
          -background SkyBlue   -flatten   flatten_padding.gif

[IM 输出]
当然,有更好的方法可以为图像添加填充,让 IM 自动把图像居中放在更大的区域中。 奇怪的是,完全相同的处理也可以用来把图像“裁剪”或裁剪到一个小于原图像的虚拟画布。在这种情况下,你需要使用负偏移来定位“裁剪”位置,因为你是在偏移图像,而不是定位裁剪“窗口”。 |

  magick logo:  -repage 100x100-190-60  -flatten  flatten_crop.gif

[IM 输出]
当然,视口裁剪也能更好地完成这件事,不需要像「[-flatten](https://imagemagick.org/command-line-options/#flatten)」那样额外进行画布生成和叠加处理。如果图像只部分位于该视窗内,它也不会把图像本身“扩展”到覆盖整个视口。「[-flatten](https://imagemagick.org/command-line-options/#flatten)」运算符的一个常见误用是从图像中移除透明度。也就是通过把图像叠加到背景色上,去掉图像可能带有的任何透明度。不过一旦涉及多张图像,这种做法就不起作用,因此不再推荐。

Mosaic - 画布扩展

[-layers](https://imagemagick.org/command-line-options/#layers) **mosaic**」运算符(或它的「[-mosaic](https://imagemagick.org/command-line-options/#mosaic)」快捷形式)更像是Flatten 运算符的扩展画布版本。它不是只根据初始图像的画布尺寸来创建初始画布,而是由 Mosaic 运算符创建一个足以容纳所有图像的画布(只向正方向扩展)。例如,这里我甚至没有设置合适的虚拟画布,但「[-mosaic](https://imagemagick.org/command-line-options/#mosaic)」运算符会算出这个画布需要多大,才能容纳所有图像层。 |

  magick \( -page +5+10  balloon.gif \)   \( -page +35+30 medical.gif \)  \
          \( -page +62+50 present.gif \)   \( -page +10+55 shading.gif \)  \
          -background dodgerblue  -layers mosaic  mosaic.gif

[IM 输出]
| _在 IM v6.3.6-2 中,「[-mosaic](https://imagemagick.org/command-line-options/#mosaic)」运算符只是「[-layers](https://imagemagick.org/command-line-options/#layers) 'mosaic'」的一个别名。

因此,「[-mosaic](https://imagemagick.org/command-line-options/#mosaic)」选项可以看作同名「[-layers](https://imagemagick.org/command-line-options/#layers)」方法的快捷方式。_
---|---
注意,「[-mosaic](https://imagemagick.org/command-line-options/#mosaic)」和「[-flatten](https://imagemagick.org/command-line-options/#flatten)」仍然都会创建一个从“原点”即 0,0 像素开始的画布。这是图像“虚拟画布”或“页面”定义的一部分,因此你可以确定,这两个运算符生成的最终图像都不会带有虚拟偏移,而且整个画布都会由实际像素数据完整定义。还要注意,「[-mosaic](https://imagemagick.org/command-line-options/#mosaic)」只会向正方向扩展画布(下边缘或右边缘),因为上边缘和左边缘固定在虚拟原点。这当然意味着,「[-mosaic](https://imagemagick.org/command-line-options/#mosaic)」仍会裁掉带负偏移的图像…… |

  magick \( -page -5-10  balloon.gif \)   \( -page +35+30 medical.gif \)  \
          \( -page +62+50 present.gif \)   \( -page +10+55 shading.gif \)  \
          -background dodgerblue  -mosaic  mosaic_clip.gif

[IM 输出]

合并 - 创建新的图层图像

[-layers](https://imagemagick.org/command-line-options/#layers) **merge**」运算符与前面的运算符几乎相同,是在 IM v6.3.6-2 中加入的。它只创建一个刚好足以按各自偏移容纳所有给定图像的画布图像。与 Mosaic 一样,它也会扩展画布,但不仅向正方向,也向负方向扩展。基本上,这意味着在把图层图像合并到一起时,你不必担心裁剪、偏移或其他方面。所有图像都会按彼此的位置关系相对合并。输出不会包含原点,也不会保证原点属于扩展后的画布。因此,Layers Merge 的输出可以包含一个“图层偏移”,它可能为正,也可能为负。换句话说,Layers Merge 会合并图层图像以生成一个新的_图层图像_。因此,如果完成后不想保留这个偏移,通常会希望在最终保存前加入一个「[+repage](https://imagemagick.org/command-line-options/#repage)」运算符。例如,下面仍是我们前面用过的同一组图层图像…… |

  magick \( -page +5+10  balloon.gif \)   \( -page +35+30 medical.gif \)  \
          \( -page +62+50 present.gif \)   \( -page +10+55 shading.gif \)  \
          -background dodgerblue  -layers merge  +repage layers_merge.gif

[IM 输出]
可以看到,图像的大小只是刚好容纳这些相互相对放置的图像,同时我丢弃了结果图像相对于虚拟画布原点的偏移。能够在不裁剪、也不留下额外无用空间的情况下保留相对位置,正是这个变体强大的地方。让我们给其中一张图像一个负偏移,再试一次…… |

  magick \( -page -5-10  balloon.gif \)   \( -page +35+30 medical.gif \)  \
          \( -page +62+50 present.gif \)   \( -page +10+55 shading.gif \)  \
          -background dodgerblue  -layers merge  +repage layers_merge_2.gif

[IM 输出]
可以看到,“balloon”没有被裁剪,只是为了保留它与其他图像的相对距离而被移得更远了。当然,上面示例中的「[+repage](https://imagemagick.org/command-line-options/#repage)」运算符会在最终图像中移除绝对的虚拟画布偏移,只保留图像之间的相对摆放关系。之所以移除这个偏移,是因为除非属于 GIF 动画的一部分,网页浏览器通常很难处理图像偏移,尤其是负的图像偏移。但如果不移除这个偏移,所有图像都会在生成的单图层图像里的虚拟画布上保持正确位置,让你可以继续处理并向合并图像中加入更多图像。通常会把「[-background](https://imagemagick.org/command-line-options/#background)」颜色设为「None」,使合并图像中未使用的区域透明。应用到单张图像时,Layer Merging 会用纯色背景替换图像中的任何透明度,但保留图像原始尺寸以及该图像中的所有偏移。不过,图像的虚拟画布尺寸可能会被调整为“最适合”该图像的尺寸和偏移。这个运算符最初的目的,是让用户更容易把多张经过扭曲的图像合并成一个统一整体,而不用管各个图像的偏移。例如,把照片对齐以组成更大的“全景图”时,就可以从一张居中的、未扭曲的基础图像(没有偏移)开始,然后用这个运算符把已经对齐并扭曲到匹配中心图像的其他图像叠加到它周围(使用负偏移或正偏移)。关于通过扭曲图像来对齐共同控制点并使用此运算符的其他示例,请参见 3D 等距照片立方体3D 透视盒。此运算符的其他示例还包括生成一组简单的重叠照片

「-layers trim-bounds」操作可用于确保所有图像
在最小画布尺寸上获得正偏移,同时保留它们的
相对位置,而且并不实际把这些图层合并成一张
最终图像。

这样,你就可以在图像真正合并之前继续处理它们,
例如相对于该图像组放置更多图像,
同时查询结果虚拟画布的边界。

不过,如果图像带有透明度,最好先从图像中裁掉
这些透明区域,理想用法如下……

  -alpha set -bordercolor none -border 1x1 -trim -layers trim-bounds

这会把图像图层最小化,包括实际图像数据中的
所有透明区域,同时确保所有内容都包含在一个有效的、
尺寸最小的虚拟(正)画布中。

Coalesce 合成 - 渐进式图层化

[-layers](https://imagemagick.org/command-line-options/#layers) **coalesce**」图像运算符(或它的「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」快捷形式)本来是为把 GIF 动画转换为图像序列而设计的。示例请参见合并动画帧。不过,它与「[-flatten](https://imagemagick.org/command-line-options/#flatten)」关系非常紧密,并且在多图层图像方面有非常有用的效果。例如,对单张图像使用 Coalesce,会与使用 Flatten 并把「[-background](https://imagemagick.org/command-line-options/#background)」颜色设为「None」或「Transparency」做完全相同的事情。也就是说,它会用透明像素“填满”图像的画布。 |

  magick \( -page 100x100+5+10 balloon.gif \) -layers coalesce  coalesce_canvas.gif

[IM 输出]
处理由多个图层组成的图像时,可以使用 Coalesce 生成图像的“渐进式图层化”。但要做到这一点,需要采取一些预防措施,禁用这个运算符对“GIF 动画”的处理。

   magick \( -page 100x100+5+10 balloon.gif \)   \( -page +35+30 medical.gif \)  \
           \( --page +62+50       present.gif \)   \( -page +10+55 shading.gif \)  \
           -set dispose None  -coalesce  miff:- |\
     montage - -frame 4 -tile x1 -geometry +2+2 \
             -background none -bordercolor none  coalesce_none.gif

[IM 输出]

在上面的示例中,我们用「[-set](https://imagemagick.org/command-line-options/#set)」把所有「[-dispose](https://imagemagick.org/command-line-options/#dispose)」设置都设为「[None](anim_basics.html#none)」。这实际上是在告诉「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」只把每一帧叠加到前面叠加结果之上。结果是,第一张图像只是用透明背景“填满”了图像画布。下一张图像是在前一张图像上叠加该图层。以此类推。这就是对图像序列做“渐进式”的 flatten。因此,序列中的最后一张图像会与在透明背景上执行普通「[-flatten](https://imagemagick.org/command-line-options/#flatten)」得到的结果相同。如果使用「[-dispose](https://imagemagick.org/command-line-options/#dispose)」设置「[Background](anim_basics.html#background)」,会得到完全不同的效果。在这种情况下,「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」只会“填满”每张图像的画布,就好像它们是完全独立的图像一样!

  magick \( -page 100x100+5+10 balloon.gif \)   \( -page +35+30 medical.gif \)  \
          \( --page +62+50       present.gif \)   \( -page +10+55 shading.gif \)  \
          -set dispose Background  -coalesce  miff:- |\
    montage - -frame 4 -tile x1 -geometry +2+2 \
            -background none -bordercolor none  coalesce_bgnd.gif

[IM 输出]

不过请注意,与 FlattenMosaicMerge 不同,「[-coalesce](https://imagemagick.org/command-line-options/#coalesce)」运算符_不会_使用当前的「[-compose](https://imagemagick.org/command-line-options/#compose)」alpha 合成设置。它只使用「[Over](compose.html#over)」合成方法,因为这是处理 GIF 动画所需要的。在更标准的图像图层化运算符中使用不同的「[-compose](https://imagemagick.org/command-line-options/#compose)」方法,就是下一组示例的主题。

合成方法与图层化

三种图层化方法:FlattenMosaicMerge,都会使用「[-compose](https://imagemagick.org/command-line-options/#compose)」设置来确定按顺序叠加每张图像时使用的合成方法。因此,你可以把这些函数看成一种多图像版的「[-composite](https://imagemagick.org/command-line-options/#composite)」运算符,并且能够设置指定颜色的初始「[-background](https://imagemagick.org/command-line-options/#background)」画布。不过,使用默认 Alpha 合成Over」以外的任何方法前,都需要先想清楚,否则会得到意外结果。你可能还需要考虑这些运算符用来生成起始画布的「[-background](https://imagemagick.org/command-line-options/#background)」颜色,会怎样影响之后合成到其上的每张图像(包括第一张)。例如,让我们使用「[DstOver](compose.html#dstover)」,把每一张后续图像放到前面图像的_下面_…… |

  magick \( -page 100x100+5+10 balloon.gif \)   \( -page +35+30 medical.gif \)  \
          \( --page +62+50       present.gif \)   \( -page +10+55 shading.gif \)  \
          -background none  -compose DstOver  -flatten  flatten_dstover.gif

[IM 输出]
这里把背景设为透明,否则结果中只能看到背景画布,因为其他所有图像都会被放在这个初始画布的“下面”!这确实提供了一种用特定颜色“清空”图像的方法,如按现有图像定尺寸的画布中所示。下面是一个更实用的例子。在某些图像处理场景中,先放背景画布再分层叠图显得别扭又不自然;你可以直接按从上到下,或从前景到背景的顺序生成图像。 |

  magick rose: -repage +10+10 \
          \( +clone -background black -shadow 60x3+5+5 \) \
          \( granite: -crop 100x80+0+0 +repage \) \
          -background none  -compose DstOver -layers merge layer_dstover.gif

[IM 输出]
前三行各生成一张图层图像,最后一行把所有图层合并到前面图层的下面,实际上反转了顺序。

可以看到,上面的图像处理比通常生成阴影时看到的做法更简单、更干净,只是依次把每张图像垫在下面(起始画布透明)。

当然,我也可以同样轻松地改为反转图像列表。 |

  magick rose: -repage +10+10 \
          \( +clone -background black -shadow 60x3+5+5 \) \
          \( granite: -crop 100x80+0+0 +repage \) \
          -reverse -layers merge layer_reverse.gif

[IM 输出]
不过请记住,这只会重新排列已有图像,并不会影响图层化方法创建的“起始背景画布”。合成方法还可以用来产生一些有趣的效果。例如,如果画三个圆,再用「Xor」合成方法把它们叠加起来,就能用很少的工作量得到一个不寻常且看起来复杂的符号。 |

  magick -size 60x60 \
          \( xc:none -fill blue   -draw 'circle 21,39 24,57' \) \
          \( xc:none -fill red    -draw 'circle 39,39 36,57' \) \
          \( xc:none -fill green  -draw 'circle 30,21 30,3'  \) \
          -background none  -compose Xor   -flatten  flatten_xor.png

[IM 输出]

Layers Composite - 合并两个图层列表

从 IM v6.3.3-7 起,「[-layers](https://imagemagick.org/command-line-options/#layers)」方法中加入了「**Composite**」,可以把两组完全独立的图像合成到一起。要在命令行中这样做,需要一个特殊的「[null:](files.html#null)」标记图像,用来定义第一组_目标_图像列表在哪里结束,以及叠加的_源_图像列表从哪里开始。不过这也是这个方法唯一真正复杂的地方。基本上,第一组列表中的每张图像都会与第二组列表中对应的图像合成,从而有效地把两个列表合并在一起。第二个列表可以使用几何偏移,像普通的 Composite 运算符(见上文)一样相对于第一个列表整体定位。Gravity 也会使用第一张图像的画布尺寸来进行计算。在这个“全局偏移”之上,每张图像各自的虚拟偏移也会被保留,因为每一对图像都是一起合成的。还有一个特殊情况也会被处理。如果其中一个图像列表只包含一张图像,那么这张图像会与另一个列表中的所有图像合成。在这种情况下,较大列表的图像元数据(例如动画计时)会被保留下来,即使它不是合成的目标一侧。
这个图层运算符更常用于合成两个动画,而动画可以看作一种按时间排列的图层图像列表。因此,它更适合在示例的动画修改一节中说明。更多细节请参见多图像 Alpha 合成


处理图像图层

使用上面各种图层运算符来叠放多张图像,是一种非常灵活的技巧。它让你可以分别处理大量图像,完成后再把它们全部组合成一个统一整体。到目前为止,我们已经展示了用许多不同方式合并(合成或图层化)多张图像的方法。这里再给出一些更实际的例子,说明如何使用这些技巧。

缩略图的图层化

你也可以使用这种技巧,以各种复杂方式把多张缩略图合并到一起。这里我在读取并定位图像时给它们加上柔边,于是可以在平铺画布上生成一幅相当不错的图像组合。

  magick -page +5+5    holocaust_tn.gif \
          -page +80+50  spiral_stairs_tn.gif \
          -page +40+105 chinese_chess_tn.gif \
          +page \
          -alpha Set -virtual-pixel transparent \
          -channel A -blur 0x10  -level 50,100% +channel \
          \( -size 200x200 tile:tile_fabric.gif -alpha Set \) -insert 0 \
          -background None -flatten  overlap_canvas.jpg

[IM 输出]

图像位置的计算式定位。

虚拟画布偏移(page)可以用多种方式设置。更具体地说,你可以用「[-set](https://imagemagick.org/command-line-options/#set)」逐图像设置这个属性,甚至为每一张图像计算不同的位置。例如,这里我读入一大组图像(尺寸完全相同的小图标图像),并把它们排成一个圆。

  magick {balloon,castle,eye,eyeguy,ghost,hand_point,medical}.gif \
          {news,noseguy,paint_brush,pencil,present,recycle}.gif \
          {shading,skull,snowman,storm,terminal,tree}.gif \
          \
          -set page '+%[fx:80*cos((t/n)*2*pi)]+%[fx:80*sin((t/n)*2*pi)]' \
          \
          -background none -layers merge +repage image_circle.png

[IM 输出]

上面示例的关键是「[-set](https://imagemagick.org/command-line-options/#set) page」操作,它使用归一化的图像索引(FX 表达式t/n」)为每一张图像生成一个从 0.0 到接近但不到 1.0 的值。随后用作为百分号转义的 FX 表达式,把这个值映射为图像在半径 80 像素圆上的位置(按角度)。计算得到的位置是图像左上角的位置(不是中心,尽管那只是一个简单调整),然后通过合并生成新图像。定位时不关心偏移是正还是负,这正是 Merge 图层运算符的威力。也就是说,我们按所有图像彼此的相对关系生成了一张新图像。最后的「[+repage](https://imagemagick.org/command-line-options/#repage)」会移除合并后图层图像最终产生的负偏移,因为它已不再需要,并且查看结果图像时可能造成问题。注意,第一张图像(结果中最右侧)位于所有其他图像的下方。如果希望图层关系真正循环,使最后一张图像也在第一张图像下面,你可能必须把第一张图像切成两半,并把上半部分放到序列末尾,让第一张图像的上半部分盖在最后一张图像之上,而下半部分仍位于第二张图像下面。这个技巧很强大,但它只能把图像定位到整数偏移。如果需要更精确的亚像素定位,就必须对图像做扭曲(平移),把它放到精确的亚像素位置,而不是只调整它的虚拟偏移。

增量计算的位置

在处理图像并设置其属性时,可以通过 FX 表达式访问其他图像的某些图像属性。这意味着,你可以相对于前一张图像计算出的位置来设置每张图像的位置。例如,下面会把每张图像的位置设为前一张图像的右侧。也就是前一张图像的位置加上它的宽度。

  magick rose: netscape: granite: \
          \
          +repage -set page '+%[fx:u[t-1]page.x+u[t-1].w]+0' \
          \
          -background none -layers merge +repage append_diy.png

[IM 输出]

每张图像都会被追加到前一张图像的位置:查找该位置并加上该图像的宽度。这个前一位置实际上是 IM 循环处理每张图像并设置「page」(虚拟偏移)属性时刚算出来的。结果相当于一个 DIY 的 Append 运算符,你可以在此基础上开发自己的变体。需要注意,整个序列实际上会被第一张图像位置计算期间设置的「u[-1].w」整体平移。这应该是当前图像序列中最后一张图像的宽度。不过这个整体位移会被最后的「[+repage](https://imagemagick.org/command-line-options/#repage)」丢弃。可以用额外计算让它忽略这个偏移,但在上面的例子中没有必要。 | _使用像「u[t]」这样的图像索引时,所有图像选择器「u」、「v」和「s」都会按给定的「[index]」引用同一张图像。因此最好把「u」(第一张或第零张图像)作为这种索引行为的助记符来使用(也防止将来行为变化)。

更多信息请参见 FX,DIY 图像运算符。_
---|---
下面是另一个例子。每张图像都会使用前一张图像的位置和宽度,相对于前一张图像偏移,从而计算出重叠追加

magick font_[0-9].gif \
        -set page '+%[fx:u[t-1]page.x+u[t-1].w-8]+%[fx:u[t-1]page.y+4]' \
        -background none -layers merge +repage append_offset.gif

[IM 输出]

这种访问其他图像属性的能力,也包括访问其他图像的像素数据。也就是说,你可以创建一张特殊图像,让其中的颜色值代表其他图像的“映射位置”。当然,这张“映射”图像本身也会被定位,而且需要在执行叠加之前移除。创建这种特殊的“映射位置”图像到底有多有用,是另一个问题。它只是另一种可能性。

图像的两阶段定位

可以把图像处理拆成两个步骤来简化流程。一个步骤用于生成、扭曲、定位图像并添加装饰,最后一步再把它们全部合并。例如,让我们创建宝丽来缩略图,来源是照片存储中的较大原始图像,并分别处理每一张(让这个方面保持独立且简单)。

  center=0   # Start position of the center of the first image.
             # This can be ANYTHING, as only relative changes are important.

  for image in ../img_photos/[a-m]*_orig.jpg
  do

    # Add 70 to the previous images relative offset to add to each image
    #
    center=`magick xc: -format "%[fx: $center +70 ]" info:`

    # read image, add fluff, and using centered padding/trim locate the
    # center of the image at the next location (relative to the last).
    #
    magick -size 500x500 "$image" -thumbnail 240x240 \
            -set caption '%t' -bordercolor Lavender -background black \
            -pointsize 12  -density 96x96  +polaroid  -resize 30% \
            -gravity center -background None -extent 100x100 -trim \
            -repage +${center}+0\!    MIFF:-

  done |
    # read pipeline of positioned images, and merge together
    magick -background skyblue   MIFF:-  -layers merge +repage \
            -bordercolor skyblue -border 3x3   overlapped_polaroids.jpg

[IM 输出]

上面的脚本看起来复杂,其实并不复杂。它只是循环生成每张缩略图,同时对每张图像做居中填充(使用 Extent)并裁剪,让图像的“中心”位于虚拟画布上的已知位置。实际上也可以计算这个位置,不过那可能需要临时文件,所以最好确保所有图像都位于一个已知位置。然后使用相对「[-repage](https://imagemagick.org/command-line-options/#repage)」运算符(参见画布偏移)平移图像,使每张生成的图像都正好位于前一张图像右侧 60 像素处。也就是说,各图像中心之间保持固定间距,不受图像实际尺寸影响,即使尺寸可能因宽高比和旋转而改变。这个脚本的另一个主要技巧是,不必把每张“图层图像”保存到临时文件,而是可以使用 MIFF: 文件格式把图像写入管道。这种方法称为 MIFF 图像流。它之所以可行,是因为「MIFF:」文件格式允许你把多张图像简单串接成一个数据流,同时保留所有图像元数据,例如虚拟画布偏移。这个技巧为许多其他脚本提供了很好的起点。图像可以生成或修改,最终尺寸和位置也可以用任何你喜欢的方式计算。另一个例子是脚本「[hsl_named_colors](../static/img/scripts/hsl_named_colors)」,它会取出 ImageMagick 中的命名颜色列表,并按 HSL 色彩空间把它们整理成这些颜色的图表。你可以在颜色指定中看到它的输出。其他可能性包括……

  • 使用任何类型的缩略图(或其他装饰),也可以直接使用原始的小缩略图。
  • 生成图像,使第一张图像居中,而其他图像像金字塔一样排列在第一张图像下方的左右两侧。
  • 按彼此相对的特定 X、Y 坐标放置图像,把它们排成弧线、圆或螺旋。例如:PhD CircleSunset FlowerFibonacci Spiral
  • 按颜色放置图像。例如:Book Covers
  • 按一天中的时间或提交时间放置图像。例如:Year of Sunsets

基本上,你可以完全自由地在虚拟画布上定位图像,然后只需把计算容纳所有图像所需的最终画布尺寸交给 IM 即可。

地图上的图钉

这里是一个典型的图层化示例,把彩色图钉放在地图上的指定位置。[IM 输出] 左边是一张“图钉”图像。图钉尖端位于 +18+41。我还有一张威尼斯地图,想在地图上的多个点放置图钉。例如,“Accademia”位于像素位置 +160+283。要把图钉对齐到这个位置,需要从地图位置中减去图钉尖端的位置。这样会得到我们这张“图钉”图像的偏移 +142+242。下面是使用图层图像得到的结果

  magick map_venice.jpg    -page +142+242 push_pin.png \
          -flatten  map_push_pin.jpg

[IM 输出]

这个示例来自 IM 论坛讨论:Layering Images with Convert让我们进一步自动化。 我们有一个文件,列出了要放在地图上的每个图钉的位置和颜色。文件中的地点名称不会被使用,只是对所列像素位置的参考注释。

[数据文件]

让我们读取这个文本文件,在循环中创建“图钉”。

  pin_x=18  pin_y=41

  cat map_venice_pins.txt |\
    while read x y color location; do

      [ "X$x" = "X#" ] && continue   # skip comments in data

      x=$(( x - pin_x ))    # magick x,y to pin image offsets
      y=$(( y - pin_y ))

      # magick 'color' to settings for color modulate (hue only)
      # assumes a pure 'red' color for the original push pin
      mod_args=$(
         magick xc:$color -colorspace HSL txt: |
           tr -sc '0-9\012' ' ' |\
             awk 'NR==1 { depth=$3 }
                  NR==2 { hue=$3;
                          print  "100,100,"  100+200*hue/depth
                        }'; )

      # re-color and position the push pin
      magick push_pin.png -repage +${x}+${y} -modulate $mod_args miff:-

    done |\
      # read pipeline of positioned images, and merge together
      magick map_venice.jpg  MIFF:-  -flatten  map_venice_pins.jpg

[IM 输出]

注意,它假设原始图钉颜色是红色(色相为 0),并使用 Modulate 运算符通过适当的缩放计算把它重新着色为其他颜色。还要注意,不改变色相(no-op)的 modulate 参数为 100,并会按 200 的值循环(一种伪百分比值)。未来:对地图做透视扭曲,根据地图上的“深度”调整图钉大小,计算扭曲导致的图钉位置变化,并把图钉“钉”到扭曲后的地图上。 上面使用了一种称为 MIFF 图像流的方法:在循环中逐个生成图像,然后把它们“管道”到“图层化”命令中生成最终图像。另一种方法(PHP 脚本中常见)是使用“生成命令”技巧,也就是用 shell 脚本生成一条很长的待执行「magick」命令。图像扭曲动画中的脚本使用的就是这种技巧。两种方法都避免了生成临时图像。

阴影图层

在一组重叠图像中正确处理半透明阴影效果,其实比看起来难得多。只是把带阴影的照片叠起来,会导致阴影被应用两次。也就是说,两个重叠的阴影会变得非常暗,而现实中阴影并不会像重叠的图像那样以完全相同的方式叠加。图像的各个部分应该只是有阴影或没有阴影。也就是说,阴影只应该对图像的任何部分应用一次。除非你有两个独立光源,否则不应该出现更暗的区域,而那还会让事情更复杂。Tomas Zathurecky < tom @ ksp.sk > 接下了在图层图像中处理阴影效果的挑战,并开发了图像累加器技术来解决这个问题。基本上,我们需要每次把一张图像加入到堆栈底部。在加入新图像时,所有前面图像的阴影都需要先把新图像变暗,然后再把它加入堆栈。不过,只应加入落在新图像上的阴影。没有落在新图像上的阴影需要先忽略,直到之后它落到其他图像或背景(如果有)上。下面是一个例子…… |

  magick \
    \( holocaust_tn.gif -frame 10x10+3+3 \
          -background none  -rotate 5 -repage +0+0 \) \
    \
    \( spiral_stairs_tn.gif -frame 10x10+3+3 \
          -background none -rotate -15 -repage -90+60 \) \
    \( -clone 0   -background black -shadow 70x3+4+7 \
       -clone 1   -background black -compose DstATop -layers merge \
       -trim \) \
    \( -clone 2,0 -background none  -compose Over -layers merge \) \
    -delete 0--2 \
    \
    \( chinese_chess_tn.gif -frame 10x10+3+3 \
          -background none -rotate 20 -repage +60+90 \) \
    \( -clone 0   -background black -shadow 70x3+4+7 \
       -clone 1   -background black -compose DstATop -layers merge \
       -trim \) \
    \( -clone 2,0 -background none  -compose Over -layers merge \) \
    -delete 0--2 \
    \
    \( +clone -background black -shadow 70x3+4+7 \) +swap \
    -background none -compose Over -layers merge +repage \
    layers_of_shadows.png

[IM 输出]
上面的程序看起来复杂,但其实相当直接。第一张图像用于开始一个累积的图像堆栈(图像索引 #0)。注意,如果不想用第一张图像初始化堆栈,也可以从单个透明像素(「-size 1x1 xc:none」)开始。现在,要向图像堆栈底部加入一张新图像,每次都应用同一组操作……

  • 首先,把缩略图图像读入内存,并应用任何旋转、相对摆放(可能为负)。如果愿意,也可以在此时对图像执行其他缩略图处理操作,尽管在这个例子中那些操作已经完成。新图像形成图像索引 #1。
  • 现在取出前一个图像堆栈(#0),用合适的颜色、模糊、偏移和环境光百分比生成阴影。
  • 这道阴影会叠加到新图像(#1)上,因此只保留落在新图像「[ATop](compose.html#atop)」上的阴影。我们还会(可选地)对结果应用裁剪操作,移除阴影操作额外加入的空间,形成图像 #2。
  • 然后只需把新图像(#2)加入累积图像堆栈(#0)。
  • 并删除除最后一张之外的所有先前工作图像。

要加入更多图像,基本上只要重复上面的操作块即可。所有图像都加入堆栈后,只需对累积的图像堆栈执行普通的阴影操作,并移除任何残留的图像偏移(许多网页浏览器很讨厌它们)。使用 Merge 可以自动处理虚拟偏移,尤其是负偏移,让你只需按相对于前面图像摆放的位置,把图像放到任何想放的地方。它也能正确应用会生成带负偏移较大图像的阴影。
现在,上面的做法能正确处理多图层图像的阴影,但虽然阴影有偏移,实际上所有图像的阴影偏移都相同!真正应该发生的是,阴影落到堆栈中越深的图像上时,偏移应越大,也越模糊。也就是说,与最底部的图像相比,顶部图像应该在背景上投下非常模糊的阴影。实际做起来更难,因为不仅要跟踪图像堆栈,还要随着图像堆栈变大,跟踪阴影已经变得多“模糊”。因此实际上需要两个累加器:一个是(如上所示的)图像堆栈,另一个是在加入更多图像时的阴影累积。例如,下面是同一组图像,但阴影会随着深度增加而变得更模糊。 |

  magick xc:none xc:none \
    \
    \( holocaust_tn.gif -frame 10x10+3+3 \
          -background none  -rotate 5 -repage +0+0 \) \
    \( -clone 1   -background black -shadow 70x0+0+0 \
       -clone 2   -background black -compose DstATop -layers merge \
       -clone 0   -background none  -compose Over    -layers merge \) \
    \( -clone 2,1 -background none  -compose Over    -layers merge \
                  -background black -shadow 100x2+4+7 \) \
    -delete 0-2 \
    \
    \( spiral_stairs_tn.gif -frame 10x10+3+3 \
          -background none -rotate -15 -repage -90+60 \) \
    \( -clone 1   -background black -shadow 70x0+0+0 \
       -clone 2   -background black -compose DstATop -layers merge \
       -clone 0   -background none  -compose Over    -layers merge \) \
    \( -clone 2,1 -background none  -compose Over    -layers merge \
                  -background black -shadow 100x2+4+7 \) \
    -delete 0-2 \
    \
    \( chinese_chess_tn.gif -frame 10x10+3+3 \
          -background none -rotate 20 -repage +60+90 \) \
    \( -clone 1   -background black -shadow 70x0+0+0 \
       -clone 2   -background black -compose DstATop -layers merge \
       -clone 0   -background none  -compose Over    -layers merge \) \
    \( -clone 2,1 -background none  -compose Over    -layers merge \
                  -background black -shadow 100x2+4+7 \) \
    -delete 0-2 \
    \
    \( -clone 1 -background black -shadow 70x0+0+0 \
       -clone 0 -background none -compose Over -layers merge \) \
    -delete 0-1 -trim +repage \
    layers_of_deep_shadows.png

[IM 输出]
仔细看这个结果。阴影的偏移和模糊程度在图像的不同部分并不相同。相邻图层的图像之间它很薄,但当它落在某幅图像上,甚至落到更深处的背景上时,就会变得很厚。当然,在这个例子里阴影偏移大概偏大,但结果看起来很真实,能更好地表现图层的深度感。注意这里把阴影操作拆成了两个步骤。把累积阴影(图像索引 #1)应用到新图像(#2)时,只加入环境光百分比,不加任何模糊或偏移(本例为 '70x0+0+0')。然后把新图像加入累积中的图像堆栈(#0)。但在把新图像(#2)的阴影直接加入累积阴影(#1)之后,同样不带模糊或偏移,这时才对所有阴影进行模糊和偏移,形成新的累积阴影图像。换句话说,随着堆栈越来越厚,累积阴影图像会越来越模糊,偏移也越来越大。只有更深处图像的阴影还没有累积那么多这种效果。这个程序实质上把阴影的应用与递增式阴影累加器分离开来。这样你就可以控制诸如……

  • 真实感阴影(如上): 70x0+0+0 和 100x2+4+7
  • 恒定阴影(基础示例): 70x2+4+7 和 100x0+0+0
  • 恒定模糊,但偏移累积: 70x2+0+0 和 100x0+4+7
  • 同时包含恒定偏移和渐进偏移: 60x0+4+7 和 100x0+1+1
  • 累积的环境光效果: 80x0+0+0 和 95x2+4+7

它们大多可能并不真实,但在别的场景里也许会很好看。另外,先设置 "-background" 颜色再进行 "-compose ATOP" 合成,可以让你定义阴影的颜色(实际上是带颜色的环境光)。你甚至可以给最终落在背景图层上的阴影使用另一种颜色(最后那个 "-background black" 设置),或者完全省略它,让图像看起来根本不在任何背景之上(也就是悬在半空中)。它的用法非常灵活。
Tomas Zathurecky 接着又开发了另一种处理分层图像阴影的方法:把分层图像列表作为一个整体来处理。这是我自己不会想到的做法。这个方法的优点是可以把整组图像作为整体处理,而不必一次累积一张图像,也不必一遍又一遍重复同一块操作。我们先再次看看较简单的“恒定阴影”问题。 |

  magick \
    \( holocaust_tn.gif -frame 10x10+3+3 \
          -background none  -rotate 5 -repage +0+0 \) \
    \( spiral_stairs_tn.gif -frame 10x10+3+3 \
          -background none -rotate -15 -repage -90+60 \) \
    \( chinese_chess_tn.gif -frame 10x10+3+3 \
          -background none -rotate 20 -repage +60+90 \) \
    \
    -layers trim-bounds \
    \
    \( -clone 0--1 -dispose None -coalesce \
       -background black -shadow 70x2+4+7 \
       xc:none +insert null: +insert +insert xc:none \) \
    -layers trim-bounds -compose Atop -layers composite \
    \
    -fuzz 10% -trim \
    -reverse -background none -compose Over -layers merge +repage \
    coalesced_shadows.png

[IM 输出]
第一组运算符只是生成分层图像列表。它也可以像前面展示的那样,用单独的程序循环来完成。操作从 "-layers trim-bounds" 开始,这是一个边界修剪操作,会扩展所有图像的虚拟画布,使其能够包含全部图像,并确保所有偏移都是正值。随后克隆这组图像,对其进行合并(Coalesced)并添加阴影,从而创建一份单独逐步推进的阴影列表。现在可以用图层合成把阴影和原始图像列表合并在一起。这里复杂的地方在于,合并之前不仅要加入一个特殊的 'null:' 标记图像来分隔两个列表,还要加入一个特殊的空白图像 'xc:none' 来偏移阴影列表。这样每个阴影图像都会以 '[ATop](compose.html#atop)' 方式覆盖到原始列表的下一幅图像上。剩下的工作就是把现在已正确加上阴影的图像按从下到上(Reverse)的顺序合并起来。
要处理“深层阴影”,需要使用图层计算。 |

  magick \
    \( holocaust_tn.gif -frame 10x10+3+3 \
          -background none  -rotate 5 -repage +0+0 \) \
    \( spiral_stairs_tn.gif -frame 10x10+3+3 \
          -background none -rotate -15 -repage -90+60 \) \
    \( chinese_chess_tn.gif -frame 10x10+3+3 \
          -background none -rotate 20 -repage +60+90 \) \
    \
    \( -clone 0--1 \
       -set page '+%[fx:page.x-4*t]+%[fx:page.y-7*t]' -layers merge \) \
    -layers trim-bounds +delete \
    \
    \( -clone 0--1 \
       -set page '+%[fx:page.x-4*t]+%[fx:page.y-7*t]' \
            -dispose None -coalesce \
       -set page '+%[fx:page.x+4*t]+%[fx:page.y+7*t]' \
            -background black -shadow 70x2+4+7 \
       xc:none +insert null: +insert +insert xc:none \) \
    -layers trim-bounds -compose Atop -layers composite \
    \
    -fuzz 10% -trim \
    -reverse -background none -compose Over -layers merge +repage \
    coalesced_deep_shadows.png

[IM 输出]
可以看到前面用过的同一组块,只是用于设置初始边界修剪,以及随后计算“渐进阴影列表”所需偏移的计算要复杂得多。不过,目前阴影还不会随着深度增加而变得更模糊。 | 如果使用 IMv7 的 "magick" 命令,上面的做法会简单得多,因为它允许你在 "-shadow" 的参数中直接使用“fx 计算”。这样不仅可以按深度计算更大的阴影偏移,也可以让阴影随深度变得更模糊。
---|---

定位扭曲后的透视图像

对扭曲图像进行对齐可能很麻烦;这里我会看看如何把这类图像对齐到一个非常具体的位置。这里有两幅图像,每幅都标出了一个特定的点。

[IM 输出] [IM 输出]

第二幅图像为 65% 半透明,所以把它合成到蓝色图像上时可以透过去看,从而判断标记点是否对齐。标记的控制点本身分别位于坐标 59,26(蓝色)和 35,14(红色)。如果只是简单叠加这两幅图像,可以直接相减偏移量,并把两幅图像彼此“合成”到一起,得到偏移 +24+12。 |

  magick align_blue.png align_red.png -geometry +24+12 \
          -composite align_composite.png

[IM 输出]
注意,这个偏移也可能是负数!这点我们马上会处理。这种方法之所以可行,只是因为这些坐标是整数像素坐标。如果匹配坐标是子像素位置(在照片拼贴中通常如此),简单合成就行不通。如果涉及任何形式的扭曲(真实照片里也很常见),它同样不会很好地工作。这正是我们要探讨的问题。
扭曲图像时,你会希望这两个像素仍然保持对齐。最好的做法是把要对齐的点用作扭曲控制点。这样可以确保它们被放到正确的位置。 |

  magick align_blue.png \
          \( align_red.png -alpha set -virtual-pixel transparent \
             +distort SRT '35.5,14.5  1 75  59.5,26.5' \
          \) -flatten  align_rotate.png

[IM 输出]
由于 distort 会生成带有“画布偏移”的“图层图像”,你不能简单地使用 Composite 来叠加图像(层级太低)。我们需要改用 Flatten 运算符,让它使用 distort 生成的偏移来定位图像。还要注意,我给“像素”坐标加了 0.5。这是因为像素有面积,而数学上的点没有;因此,如果要对齐一个像素的中心,就需要给像素内部中心“点”的位置加上 0.5。更多信息参见图像坐标与像素坐标。上面另一个问题是,叠加图像被蓝色背景画布图像“裁切”了,就像 Composite 运算符 所做的一样。也就是说,在合成过程中,“蓝色”图像为结果提供了“裁切视口”。为避免这种情况,我们改用图层合并,它会自动计算一个足够大的“视口”画布,能容纳所有要合成在一起的图像。 |

  magick align_blue.png \
          \( align_red.png -alpha set -virtual-pixel transparent \
             +distort SRT '35.5,14.5  1 75  59.5,26.5' \
          \) -background none -layers merge +repage  align_rotate_merge.png

[IM 输出]
作为“merge”的结果,图像会带有一个“负”偏移(用于保留图像的图层位置)。为了显示结果,我必须丢弃这个偏移,因为许多浏览器无法处理图像中的负偏移。我是在保存最终图像前使用 "+repage" 做到这一点的。如果还要继续处理(而不是在网页上显示结果),我会保留这个偏移(去掉 "+repage"),这样图像位置在后续处理中仍会处于正确且已知的位置。
现在,如果你要做更复杂的扭曲,例如透视,上面展示的同样技巧也适用。 |

  magick align_blue.png \
          \( align_red.png -alpha set -virtual-pixel transparent \
             +distort Perspective '35.5,14.5  59.5,26.5
                       0,0 32,4    0,%h 14,36    %w,%h 72,53  ' \
          \) -background none -layers merge +repage  align_perspective.png

[IM 输出]
这种技术的问题在于,你要用一个内部控制点来定位透视扭曲。也就是说,一个点在图像内部,另外 3 个点在边缘周围。这会让实际的透视形状难以控制,因为任何控制点的一点小移动,都可能让那个“自由角”剧烈移动。如果你使用一大串“配准点”来获得更精确的“最小二乘拟合”以定位图像,情况可能更糟。那种情况下,你关心的点可能根本不靠近用于扭曲图像的任何一个“配准”控制点。替代做法是,先按需要的方式扭曲图像,然后再算出需要如何平移结果图像,才能对齐我们关心的点。要做到这一点,就必须知道“关注点”在扭曲后移动到了哪里。这正是扭曲并定位图像,尤其是真实照片时的真正难题。例如,这里我用四个角来扭曲图像,生成一个特定的(假定是想要的)扭曲形状,但此时还不尝试对齐控制点,只是应用这个扭曲…… |

  magick align_blue.png \
          \( align_red.png -alpha set -virtual-pixel transparent \
             +distort Perspective '0,0  10,12  0,%h 14,40
                               %w,0 68,6  %w,%h 63,48 ' \
          \) -background none -layers merge +repage  align_persp_shape.png

[IM 输出]
如你所见,红色图像已经被扭曲,但红色控制点的位置完全不在我们要对齐的蓝色控制点附近。你不能简单地测量这两个点,因为红色点不太可能正好落在精确的像素位置上,它会涉及子像素偏移。我们需要先精确计算红色点在哪里。为此,可以启用 verbose 重新运行上面的扭曲,以取得透视正向映射系数。然后可以按照透视投影扭曲中的说明,用这些系数进行计算。

  magick align_red.png  -define distort:viewport=1x1  -verbose \
          +distort Perspective '0,0  10,12  0,%h 14,40
                                %w,0 68,6  %w,%h 63,48 ' null:

[IM 文本]

我们需要的只是扭曲所用的已计算系数。因此并不需要目标图像,只需用 "null:" 图像文件格式丢弃输出。我们还通过扭曲视口告诉 distort,它正在生成的新图像只有 1 个像素大小。这样它会完成扭曲准备和 verbose 报告,但随后只扭曲一个“目标”像素,而这个像素会被丢弃。这样可以节省大量处理时间。事实上,如果这个扭曲在计算中没有用到源图像元数据(百分号转义 '%w' 和 '%h' 需要它),我们甚至不需要源图像 "align_red.png"。那种情况下,输入图像也可以使用单像素的 "null:" 图像。并且在这个信息收集步骤里,我们对虚拟像素、背景或其他任何内容都不真正关心,所以也无需担心这些功能的设置。
现在我们可以取得 distort 信息,接下来需要从输出的第 3 行和第 4 行提取 8 个透视系数。然后可以用它们把红色控制点映射到新的扭曲后位置,再用蓝色控制点减去这个位置,从而得到所需的实际平移量,让标记的红色坐标与蓝色坐标对齐。

  bluex=59; bluey=26
  redx=35; redy=14

  magick align_red.png  -verbose \
             +distort Perspective '0,0  10,12  0,%h 14,40
                               %w,0 68,6  %w,%h 63,48 ' null: 2>&1 |\
    tr -d "',"  |\
      awk 'BEGIN   { redx='"$redx"'+0.5;   redy='"$redy"+0.5';
                     bluex='"$bluex"'+0.5; bluey='"$bluey"'+0.5; }
           NR == 3 { sx=$1; ry=$2;  tx=$3; rx=$4; }
           NR == 4 { sy=$1; ty=$2;  px=$3; py=$4; }
           END { div =  redx*px + redy*py + 1.0;
                 dx = ( redx*sx + redy*ry + tx ) / div;
                 dy = ( redx*rx + redy*sy + ty ) / div;
                 printf "red point now at %f,%f\n", dx, dy;
                 printf "translate shape by %+f %+f\n", bluex-dx, bluey-dy; }'

[IM 文本]

上面的例子使用 "tr" 文本过滤器从输出中移除多余的引号和逗号。然后使用 "awk" 程序提取系数,并进行把红色标记“正向映射”到蓝色标记所需的浮点数学计算。注意,我再次给控制点的“像素坐标”加了 0.5,以确保计算使用的是像素中心。参见图像坐标与像素坐标。现在我们知道了扭曲图像所需的平移量,有两种方法可以把这个平移加入扭曲。一种是适当修改透视投影的系数(不容易)。另一种则是直接把平移量加到原始的每个目标坐标上(很容易)。下面是后一种做法(把平移加到目标坐标)的结果…… |

  magick align_blue.png \
          \( align_red.png -alpha set -virtual-pixel transparent \
             +distort Perspective '0,0   31.408223,15.334305
                                   0,%h  35.408223,43.334305
                                   %w,0  89.408223, 9.334305
                                   %w,%h 84.408223,51.334305 ' \
          \) -background none -layers merge +repage  align_persp_move.png

[IM 输出]
在右侧,我把控制点周围的结果裁剪并放大,以显示它们已经完全对齐! |

  magick align_persp_shape.png -crop 19x19+50+17 +repage \
          -scale 500%   align_persp_shape_mag.png

[IM 输出]
如你所见,两个像素已经完美对齐,没有向任何一侧产生子像素溢出。即使最微小的未对齐,也会在中心像素两侧显示为不对称的着色。这次缩放甚至显示出红色十字左右两侧因透视扭曲而产生的轻微不对称差异。也就是说,这个像素级查看测试就是如此精确。
一个类似但更简单的问题,在使用 Distort 定位文字中有介绍。


Evaluate-Sequence - 直接合并多图像的方法

[-evaluate-sequence](https://imagemagick.org/command-line-options/#evaluate-sequence)”方法用于以非常特定的方式合并多幅相同尺寸的图像。在某种意义上,它是Evaluate 与 Function 运算符和我们上面看到的多图像合成技术的结合。它提供的许多方法也可以用普通的多图像图层合成技术来完成,但不是全部。这个运算符使用与 "[-evaluate](https://imagemagick.org/command-line-options/#evaluate)" 相同的方法,因此可以用 "-list Evaluate" 取得它们的列表。不过其中一些(例如 'Mean' 和 'Medium')实际上只有配合这个运算符使用时才真正有用。

多幅图像的平均值(Mean)

本质上,较旧的 "-evaluate-sequence mean" 和较新的 "[-evaluate-sequence](https://imagemagick.org/command-line-options/#evaluate-sequence) **mean**" 都会对提供的所有图像取平均。例如,这里使用玫瑰图像的所有上下翻转和左右翻转版本来生成平均图像。 |

  magick rose: -flip rose: \( -clone 0--1 -flop \) \
          -evaluate-sequence mean  average.png

[IM 输出]
对同一个固定场景的数百幅图像取平均,可以去除大多数短暂出现的影响,例如移动中的人,使它们变得不那么重要。不过,短暂影响很多的区域可能会留下“幽灵般的模糊”,这可能很难去除。视频序列在查看单帧时通常噪声很重,因此可以把若干连续但不变化的帧平均在一起,得到更干净、更锐利的结果。亚利桑那大学的 Matt Leigh 报告说,他曾用这种技术提高显微镜图像的分辨率。他拍摄同一“目标”的多幅图像,然后把它们全部平均,以提高结果的信噪比。他认为其他人也可能会发现它对这一目的有用。把两幅图像平均到一起的另一种方法,是使用 "composite -blend 50%" 图像操作,它适用于尺寸不同的两幅图像。更多细节请参见混合两幅图像的示例。IM 讨论论坛曾有一个关于每次平均序列中的 10 帧的讨论,以便在不占满计算机内存(导致非常慢)的情况下平均数千幅图像。与此相关、并包含相关数学内容的是不要一次加载所有图像这个讨论。替代使用 'mean' 的另一种办法,是使用较新的 Poly 运算符,它可以为每幅图像单独设置权重。

多幅图像的最大值/最小值

Max” 和 “Min” 方法会从一组图像中取得最大(较亮)值和最小(较暗)值。它们基本上也等同于使用变亮和变暗合成方法,只是用于多幅图像。只要选择合适的背景画布颜色,就可以用等价的合成方法配合 Flatten 运算符来实现。 |

  magick rose: -flip rose: \( -clone 0--1 -flop \) \
          -evaluate-sequence max  max.png

[IM 输出]
|

  magick rose: -flip rose: \( -clone 0--1 -flop \) \
          -evaluate-sequence min  min.png

[IM 输出]
警告:这不是(按强度)选择像素,而是选择数值。这意味着输出图像可能会由来自不同图像的单独红、绿、蓝数值组成,从而生成输入图像中不存在的新颜色。如果需要按强度的最大/最小值选择像素,请参见按强度变亮的合成方法

按强度选取中位像素

[-evaluate-sequence](https://imagemagick.org/command-line-options/#evaluate-sequence) **Median**” 会寻找其强度位于所有给定图像中间的像素。也就是说,对于每个位置,它会收集并排序来自各图像的像素强度,然后选出序列中间位置的像素。它也可以作为简单平均一组图像像素的替代方法。例如,可以把一幅图像与上下两个“限制”图像结合使用。由于得到的是中间强度的像素,所以你会得到原始图像中的像素,或者来自“限制”图像的像素。换句话说,可以用它来“裁切”原始图像的强度。听起来奇怪,但确实如此。若图像数量为偶数,会选择中间偏亮一侧的像素。因此,只有两幅图像时,这个运算符等价于逐像素的“按强度变亮”。关键在于,每个像素都完全来自一幅图像,并按强度排序。每个像素的确切颜色完全来自给定图像之一,因此不会生成新颜色。例如,这里使用玫瑰图像的所有上下翻转和左右翻转版本,得到中位强度的像素。注意它并不那么平滑,但可能得到锐利边界,因为它基于像素强度。 |

  magick rose: -flip rose: \( -clone 0--1 -flop \) \
          -evaluate-sequence median  median.png

[IM 输出]

多幅图像相加

Add” 方法当然就是简单地把所有图像相加。

  magick ... -evaluate-sequence add ...

这是一种更快(更直接)的版本,相当于使用 Flatten 对所有图像执行 Plus 合成……

  magick ... -background black -compose plus -layers flatten ...

请注意,以这种方式相加图像很容易溢出图像的量子范围,因此除非使用 HDRI 版本的 IM,否则结果可能会被“截断”。这就是通常改用平均值,即 Mean的原因,因为它会将所有图像等分,确保结果图像不会被截断。另一种替代方案是使用较新的 Poly 运算符,它可以为每幅图像单独设置权重。

多幅图像相减

Subtract” 方法会从第一幅图像中减去每一幅图像。至少它本应如此。内部实现中参数被交换了,实际是在从下一幅图像中减去前一次结果。啊啊啊!不过,利用 Linear Burn 合成方法的一个特性,可以从第一幅图像中减去第二幅及之后的图像。基本做法是,对第一幅以外的所有图像进行取反,并把 'white'(取反后的零)设置为起始背景色,然后就可以使用 Flatten 从第一幅图像中减去所有图像。

  magick ...  \
         -negate \( -clone 0 -negate \) -swap 0 +delete \
         -compose LinearBurn -background white -flatten \
         ...

多幅图像的乘法/除法

Multiply” 和 “Divide” 会被 "[-evaluate-sequence](https://imagemagick.org/command-line-options/#evaluate-sequence)" 接受为方法,但它们会产生意外而奇怪的结果,因为它们像 "[-evaluate](https://imagemagick.org/command-line-options/#evaluate)" 一样,使用的是图像的实际颜色值,而不是规范化后的颜色值。结果就是乘法和除法的尺度过大。这可以算作一个 bug。在此期间,Multiply 最好使用等价的 'flatten' 方法,它能按预期工作。

  magick ... -background white -compose multiply -layers flatten ...

Poly - 使用多项式合并多幅图像

与 "[-evaluate-sequence](https://imagemagick.org/command-line-options/#evaluate-sequence)",尤其是 'mean' 方法(图像平均)密切相关的是 "[-poly](https://imagemagick.org/command-line-options/#poly)" 运算符(在 IM v6.8.0-5 中加入)。这个运算符会为内存中的每幅图像给出一组两个数字:一个为每幅图像提供乘法权重,另一个为每幅图像提供幂指数。这让你可以像把每幅图像当作多项式方程的变量输入一样,合并一组图像。每幅图像的颜色值会被当作规范化的 0 到 1 值来处理。对于每一对数值,图像颜色(规范化后)会先按第二个“幂”指数进行乘方,然后再按第一个数字加权(相乘)。如果指数为 '1',该值就只是乘以给定权重。但如果指数为 '0',权重会成为最终值,产生一个规范化的颜色常量加法(值从 0.0 到 1.0)。可以在当前图像序列中提供一幅单像素图像,用来加入某种特定颜色,并可让每个通道具有不同的规范化颜色值(使用权重和指数 = 1.0)。也可以提供一个 "NULL:' 图像(或任何其他无用图像),并使用指数 0.0。这样只会把给定的权重因子作为常量加入。最终图像会从第一幅图像(以及它的尺寸和其他元数据)生成,就像 FX DIY 运算符 一样。例如…… |

  magick rose: granite: null: -poly '1,1 2,1 -1.0,0' poly_rose.png

[IM 输出]
这会取一幅 'rose:'(使用权重 1 和幂 1,保持不变),向其中加入 'granite:' 图像颜色值的两倍(weight=2),最后使用 'null:' 图像,以指数 0(忽略图像输入)和权重值 -1.0 减去数值 1。得到的图像等价于……

rose + 2.0*granite - 1.0

rose + 2.0*(granite-0.5)

换句话说,玫瑰图像被叠加了一层带噪声的花岗岩纹理(带 50% 灰色偏置)。这实际上就像一种非常强的 '[Hard_Light](compose.html#hardlight)' 合成光照效果,只不过花岗岩叠加层的权重非常明确。它相对于其他多图像操作的关键区别在于,可以为每幅图像单独设置权重,同时所有计算都在一次图像处理操作中完成,不需要额外的中间图像。这样在非 HDRI 版本的 ImageMagick 中,可以避免量子舍入、截断或其他影响最终结果的效应。(参见量子效应)。例如,它可以用来对大量图像执行加权平均,比如先平均较小的图像组,再把这些组一起平均。