ImageMagick 示例 -- Windows 下的使用
[ 简介](#intro)
- 在我的 Windows PC 上使用 IM 脚本有什么用?
- IM 命令行工具的可用运行环境
- 有效地运行脚本
- Convert 的问题
- 字符编码
- 在 Windows 下安装 ImageMagick
- 特殊情况与陷阱
- 获取帮助
- 批处理文件中的文件名处理
- 批量处理多个文件
- 从 ImageMagick 获取信息
- 执行计算
- 编辑、调试与运行时错误测试
- 优化执行时间
- 批处理编程指南
- 处理多个文件
- 处理文本文件
- 管道操作
-
更多信息 IM Examples 中的大多数命令最初都是专门为 LINUX 和 UNIX shell 脚本编写的,因为这些系统在设计上就考虑了批处理和网络服务器的场景。然而,越来越多的 ImageMagick 用户希望在 Windows 环境下使用它。本节将详细介绍并示范如何在该环境下使用 IM,更重要的是,如何把 UNIX shell 命令(即 IM Examples 其他部分所使用的命令)magick 成对应的 Windows DOS 版本。我要特别感谢 Wolfgang Hugemann
,他完全重写了原始笔记,并将其扩展到涵盖了 Windows 用户特别关心的更多主题。你在这里看到的内容是他的成果,IM 用户都应感激他所花费的时间和耐心。
简介
在我的 Windows PC 上使用 IM 脚本有什么用?
以下示例基本上都假设你是在(可能已连接网络的)Windows 桌面计算机上运行 IM。当然,市面上有很多现成的图像处理程序,比如 Adobe Photoshop、Corel 的 Paint Shop Pro、IrfanView (http://www.irfanview.com/),甚至 GIMP (http://www.gimp.org/)。那为什么还要费心用 IM 的命令行程序和脚本来处理图像呢?相比鼠标驱动的界面,使用 ImageMagick 的真正优势在于,无论是针对单个文件还是大批量文件,你都可以完全自动化那些例行的操作。比如以下这些任务:
- 批量格式转换
- 相当多的 Windows 程序都能做到这一点,比如 IrfanView。但 IM 在图像格式方面的多样性是无与伦比的。例如,你可以把一份 PDF 的所有页面 magick 成一系列 JPEG(前提是你的电脑上装有 GhostScript)。
- 收缩与预处理数码照片
- 在将数码照片嵌入文字处理文档时,通常应该降低其分辨率,这样文档才能更快打印出来。如果你是通过 FreePDF 这类 PDF 打印驱动把文档 magick 成 PDF,情况也是一样。预处理还可以包括色彩校正和镜头校正例程。
将你的 logo 放到大批数码照片中
- 对大批数码照片应用一系列操作
- 你可能通过一个鼠标驱动的程序摸索出了一系列操作步骤,此后希望把这些步骤自动化,以便日后批量处理。然而,脚本语言(比如 Adobe 的 Action Script)在 Windows 图像处理程序中并不常见。
将多张图像合并为一张目录图像
尽管其中一些任务(尤其是批量缩放)也可以由其他免费软件提供(尤其是 IrfanView 的批处理功能),但你在可应用的处理步骤上永远没有完全的自由:你只能使用程序所提供的那些。例如,IrfanView 的批处理可以把一段文字放到大批照片中,但不能放置 logo。它也可以调整 gamma 值,但无法把照片的直方图两端裁切掉(IM 中的 "[-contrast-stretch](https://imagemagick.org/command-line-options/#contrast-stretch)" 就能做到这一点,参见 Normalize 和 Contrast Stretch)。IM 脚本尤其适合在公司网络中投入实际使用,因为写好的脚本可以被任何人使用——终端用户不必了解后台发生了什么。这样一来,图像上的标准工作流程步骤就能被完全自动化(并真正实现标准化)。以下介绍的许多脚本,都是我们这家小公司(从事事故重建工作)在实际生产中衍生出来的。我既不是一名出色的 Windows 脚本程序员,也算不上最熟悉 IM 命令行工具的人。以下处理的一些问题,可能存在更优雅的解法。我想表达的重点是:
- 演示一些使用 IM 命令行工具进行 Windows 脚本编程的基本技巧。
- 证明基于 IM 的脚本既不是为艺术而艺术,也不是学术性的消遣。
- 展示 IM 的命令行工具能在本地网络中(而不仅仅是在 Web 服务器上)完成真正的工作。
和 IM 其他示例页面一样,我们这里只使用 IM 的命令行工具,而不涉及其各种编程接口。这些脚本旨在运行于一个本地网络中,其中每个网络驱动器都分配了盘符。大多数脚本是打算在该网络的客户端计算机上运行,只有少数针对网络(文件)服务器。
IM 命令行工具的可用运行环境
在 Windows 下,简单的 IM 命令通常在 Windows 命令 Shell(通过启动 cmd.exe 运行的“DOS Shell”)中执行。对于在一行较长的命令行或一系列命令行中执行的复杂操作,最好写一个脚本。对于一系列简单命令,这多半会是一个在 Windows 命令 Shell 中执行的 DOS 批处理文件。不过这种方式也有其局限,因为批处理文件的命令集相较于常见 UNIX 命令 Shell 要有限得多。在 Windows 下运行 IM 时,基本上有以下几种选择:
- Windows 命令 Shell(“DOS 窗口”)
- 它由
cmd.exe(32 位模式)运行,存在于 Windows NT 4.0、Windows XP 及更高版本的所有 Windows 计算机上。参见 使用 DOS Shell 和批处理文件,以及关于 Convert 的问题 的特别说明。 - Cygwin
- 一个类似 bash 的命令 Shell (http://www.cygwin.com/)。使用这个 Shell 时,本使用说明其余部分给出的 IM 示例可以原样直接运行,因为你可以使用到 UNIX 风格的命令行 Shell。参见下文的 使用 Cygwin。
- Windows Script Host
- Windows Script Host 基于 .COM 技术。它存在于任何现代 Windows 计算机上,WSH 脚本比简单的 DOS 批处理文件强大得多。Windows Script Host 提供了多种编程接口,其中最常见的是 VBScript (Visual Basic Script) 和 JScript (Java Script)。可以通过 Shell 对象的 DOS shell 命令
Run或Exec来调用 IM 命令行工具。参见下文的 Visual Basic Script (VBS)。 - Windows PowerShell
- 古老 DOS shell 更为强大的继任者,基于 .NET 2.0 技术。PowerShell 随 Windows 7 一同发布,通过
powershell.exe运行。在 Windows XP 和 Vista 上,可以从 Microsoft 官网下载。
有效地运行脚本
假设你已经写好了一个完美的 Windows 脚本(DOS 批处理文件、VBScript,或其他任何形式),它以输入文件的名称作为命令行参数,执行一些操作,然后输出单张图像或一系列图像作为结果。你肯定不愿意每次都启动一个 DOS 命令 Shell(或其替代品),再敲入文件名交给脚本处理。为了避免这种繁琐的操作方式,基本上可以使用 拖放 或 发送到:使用 拖放 时,把 DOS 批处理文件或 VBScript(或其他任何形式)放在一个方便访问的位置,比如桌面,或者存放待处理图像文件的目录。然后在 Windows 资源管理器中选中待处理文件,直接把它们拖到脚本文件上即可。文件名会作为命令行参数交给脚本,并可在脚本中引用。另一种方式是,把脚本(或其快捷方式)放到 发送到 文件夹中。这个文件夹里的程序会出现在资源管理器文件窗格中右键点击时弹出的上下文菜单里。同样,被选中文件的名称会作为命令行参数交给脚本。“发送到”文件夹的名称是 SendTo。它的位置似乎每换一个 Windows 版本就会变动。一个万无一失的查找方式是在运行框里输入 shell:sendto。在 Windows XP 及以后版本中,单条命令行最长可以是 8192 (= 2¹³) 个字符。所以如果你直接从命令行调用 IM 工具,或者通过一个直接列出所有所需文件的批处理文件调用,基本不会碰到这个限制。但拖放用的是 ExecShell 例程,它限制字符串长度“仅”为 2048 (= 2¹¹) 个字符。由于所有文件都是以完全限定文件名(即盘符 + 路径 + 名称)传递的,当路径名过长时,这就可能给通过 WSH 运行的批处理文件和 VBScript 带来问题。这种错误一旦发生,脚本本身是无法妥善处理的,因为错误是在脚本真正开始运行之前发生的。在 Windows XP 下,通常的解决办法是把文件放到路径名更短的位置。
Convert 的问题
IM 的 Windows 安装程序会把 IM 的程序目录添加到搜索路径中,这样你就可以直接从命令提示符调用 IM 的命令行工具,而不需要指定路径名。默认情况下,你会使用 magick 作为命令行工具。但如果你选择了旧版程序名,IM 命令行工具的名称就相当通用(例如 convert、identify、compare……),这就容易与其他程序发生名称冲突。尤其是 convert,它是一个 Windows 系统工具,位于 Windows 系统目录 (c:\Windows\system32\convert.exe) 中,用于把 FAT32 文件系统转换成如今常见的 NTFS。但还有其他同样叫做 "convert.exe" 的程序,比如 Delphi 的报表转换工具。这个 FAT → NTFS 的 magick 工具最早随 Windows XP 一同发布,与 IM 的命令行工具 "convert" 产生了名称冲突。由于 IM 的程序目录是被追加到 DOS 搜索路径(即 PATH 环境变量)末尾的,系统工具会先被找到,导致旧脚本中简单的 "magick" 调用无法正确解析。不过现行版本的 IM Windows 安装程序会把 IM 的程序目录放在搜索路径的最前面,从而确保在名称冲突时,IM 的命令行工具通常会被优先找到。然而,其他同名的工具(比如 Delphi 的报表转换器)也遇到了同样的问题,并采取了同样的解决办法,即把自己的程序路径放到 path 变量最前面,这意味着这个解决方案并非万无一失:如果 Delphi 是在 IM 之后安装的,简单调用 Convert 就会启动报表转换工具,而不是 IM 的 Convert。Windows XP 引入 Convert 工具导致大量旧脚本崩溃。常见的解决办法是把 IM 的 Convert 工具改名为诸如 "IMconvert" 之类的名字(注意不能给系统命令改名,因为下一个 Windows 服务包大概率会直接把它恢复原状,无视改名后的版本)。这种解决办法虽然如今已无必要,但在互联网上仍然随处可见。要避免未来可能出现的名称冲突,最好的办法是在任何脚本中都通过完整路径名调用 IM 的命令行工具。也就是说,把它的位置存到一个变量或常量里。因此每个批处理文件都应该以类似下面这样的几行开头
SETLOCAL EnableDelayedExpansion
SET IMCONV="%PROGRAMFILES%\ImageMagick\Convert"
...
%IMCONV% -size 128x128 xc:white test.gif
上面的代码假定 IM 被安装在程序目录下一个名为 "ImageMagick" 的文件夹里,但这并不是其安装文件夹的标准命名方式。(详情参见 在 Windows 下安装 ImageMagick。)%PROGRAMFILES% 是一个环境变量,会展开为程序目录的名称,也就是英文版 Windows 下的 "Program Files",德文版 Windows 下的 "Programme"。SETLOCAL 会把新环境变量(比如 IMCONV)的定义限制在该批处理文件的作用域内。这里其实并不真正需要 EnableDelayedExpansion,但每次使用 SETLOCAL 时都加上这个选项是个好习惯;详情参见 批处理编程指南。还有一些更精巧、更万无一失的方法可以找出 IM 的安装目录,将在 编辑、调试与运行时错误测试 中介绍。等价的 VB-Script 代码大致会是这样
Set wsh = WScript.CreateObject("WScript.Shell")
IMconv = wsh.ExpandEnvironmentStrings("%PROGRAMFILES%") & "\ImageMagick\Convert"
出于简洁起见,我们在下文不会每次都用这段代码,但你应该把它当作一个好习惯记在心里。关于这个问题的一份不错的总结与解决方案,参见 Ron Savage: MS Windows and convert.exe。
字符编码
ImageMagick 使用 Unicode 编码字符串,更确切地说是 UTF-8。相反,DOS 使用代码页来编码字符(大多是 8 位)。这会在写字符串到图像中时产生问题,比如使用 'label' 或 '-title' 的场合:当使用非 ASCII 字符时,简单的做法会出问题。例如想创建包含德语变音符号 'ä'、'ö'、'ü' 的标签,在 Linux 下你可以简单地这样写……
magick label:äöü test.png
但在 Windows 下这样做不会得到想要的字符串。不过你可以从文本文件中读取 UTF-8 编码的字符串:
magick label:umlauts.txt test.png
如果事先把代码页切换成 UTF-8,你甚至可以用 "echo" 来创建这个文本文件:
CHCP 65001
ECHO äöü > umlauts.txt
magick label:umlauts.txt test.png
但如果你想处理某条 DOS 命令的输出,比如想用某个目录的名称为该目录中所含 JPEG 的索引打印图加上标题,那就麻烦了。切换到代码页 65001 在大多数 DOS 命令下都行不通,尤其是在遍历目录树的循环中。而在比如 1252(西欧拉丁语)和 65001 之间来回切换代码页,通常也行不通,或者至少会变得相当棘手。最稳妥的做法是在需要字符串时,用外部命令行程序(比如 "Iconv.exe",这是一个也有 Windows 版本的 UNIX 工具)来对其进行 magick 转换。从 SourgeForge 下载安装文件,并将其安装到标准目录 C:\Program Files\GnuWin32 中。然后在脚本里把 DOS 命令的输出转存到一个文本文件,并按如下方式将该文件 magick 成 UTF-8:
CHCP 1252
DIR /B äöü.jpg > temp.txt
"C:\Program Files\Gnuwin32\bin\iconv.exe" -f ISO-8859-1 -t UTF-8 temp.txt > title.txt
magick label:@title.txt äöü.jpg -append äöü_labelled.jpg
这些参数告诉 Iconv 把编码从 ISO-8859-1(代码页 1252)转换为 UTF-8。Iconv 把输出写到 stdout,所以你得把它重定向到一个文件,才能用于 'label'。请注意,无论如何,把输出转存到文本文件都是值得推荐的做法,因为这会让 ImageMagick 按字面意思解释文件内容,而不会把 Windows 的反斜杠 ("\") 当作转义字符。当然,上面示例中的代码本身没有太大实际意义,这里只是为了演示。在实际的 DOS 批处理文件中,文件名多半会在 FOR 循环中生成。下面给出了一个实用示例(参见 批量处理(子)目录树)。关于更多 UTF 处理内容,参见其他 IM 示例以及 Unicode 或 UTF8 格式文本处理 中的信息。
在 Windows 下安装 ImageMagick
ImageMagick 处于持续开发之中,新版本大致每月发布一次。强烈建议使用最新版本的 IM,尤其是当 IM 似乎没有按你预期的方式完成工作时。在大多数情况下,安装当前版本就能解决问题。当前二进制发行版的安装程序可以在 https://imagemagick.org/script/binary-releases.php#windows 找到。你也可以从 Astronomy and Astrophotography Blog 下载支持 HDRI(浮点质量)和 FFT(快速傅里叶变换)的 ImageMagick 版本。默认情况下,IM 会把自己安装到名为 C:\Program Files\ImageMagick x.x.x-x 的程序目录中,其中 "x.x.x-x" 代表其版本号。默认情况下,首次安装 IM 时安装程序会建议扩展 PATH 环境变量(即“将应用程序路径添加到系统路径”这一项会被勾选)。如果你忘了卸载旧版本,很快就会攒下一堆各版本的 ImageMagick,以及相应的一堆 PATH 扩展项。因此在我们办公室,我们养成了这样的习惯:把 IM 安装到 C:\Program Files\ImageMagick,新版本直接覆盖旧版本安装,PATH 环境变量则保持不动。多年使用下来,我们没有发现这种做法有任何问题。如果你确实想知道 IM 的版本号,随时可以调用 "convert -version",如果脚本依赖某个最低版本号,一个足够健壮的脚本也可以据此判断版本号。更多信息参见“编辑、调试与运行时错误测试”一节。ImageMagick 会向 HKEY Local Machine\Software\ImageMagick 写入几个注册表项。如果你需要处理多个已安装的 IM 版本,最重要的键是 HKEY Local Machine\Software\ImageMagick\Current,在其中还能找到 IM 二进制文件的路径,名为 BinPath。你可以在任何脚本开头查询这个注册表项,从而在不依赖 PATH 环境变量的情况下确定程序路径。更多信息参见“编辑、调试与运行时错误测试”一节。IM 的安装程序提供了一个选项 Install ImageMagickObject OLE Control for VBscript, Visual Basic, and WSH,用于为 ImageMagick 安装一个 COM+ 对象。这个选项默认不勾选,而且正如下文将要证明的那样,安装 COM+ 对象并不是在 VBScript 中使用 IM 的前提条件。不管怎样,你都不应该指望在自己以外的脚本目标机器上装有这个 COM+ 对象。在处理 PostScript 文件时,ImageMagick 依赖另一个程序 "Ghostscript" 来读取和转换 PostScript 与 PDF 文件为它能使用的图像格式。也就是说,要读取这类文档,你的计算机上需要安装 Ghostscript。其最新版本可以在 SourceForge 下载。安装 GhostScript 和 ImageMagick 的先后顺序没有关系。你不需要在 ImageMagick 之前安装 GhostScript,即使没有安装它,ImageMagick 也能正常运行。只有当你想处理 Postscript 或 PDF 文件时才需要它。IM 会在运行时通过查询注册表来确定 Ghostscript 的位置。
特殊情况与陷阱
作为 Windows 程序,IM 有一点相当特别:它允许把图像写到 stdout,也可以从 stdin 读取,因而可以使用管道把多个图像处理任务串联起来。下面这个操作
magick -size 128x128 xc:white gif:- | magick - test.gif
等价于
magick -size 128x128 xc:white test.gif
在管道的第一条命令中,减号运算符告诉 IM 把图像写到 stdout,而管道第二条命令中的减号运算符则告诉 IM 从 stdin 读取图像。这种做法可以避免显式使用临时文件。它在某些命令没有提供特定操作(比如裁剪)时尤其有用:
montage -tile 2x2 -geometry 400x300+60+60 1.png 2.png 3.png 4.png miff:- | magick - -trim montage.png
由于这个特性,文本输出通常会被写到 stderr 而不是 stdout。例如:如果你想把 Compare 的文本输出重定向到一个文本文件,就得这样写
magick compare -metric PSNR 1.png 2.png dif_1_2.png 2>result.txt
也就是说,你需要把 stderr("2>")而不是 stdout("1>" 或者单纯的 ">")重定向到文本文件。
获取帮助
命令行选项在 IM 官网 上有详尽的文档,但真正示范如何让它们运作起来的,是官网的 Usage 部分。网站的这一部分结构相当清晰,能让你以问题为导向来处理想要完成的任务。不过最快捷的办法是,就你想了解的命令行选项,在该网站的这一部分内做一次 Google 搜索。如果你想对多张图像做 montage 拼版,想了解 -tile 选项,可以做以下任意一种 Google 搜索
这样你很快就能了解到要点。只是要记住,这样搜到的代码是针对 UNIX / LINUX 环境编写的,要在 Windows 下应用需要做一些微调。另一个非常有用的信息来源是 IM 的讨论区,也就是 Discourse Server User Forum,你应该把它加入浏览器的书签/收藏夹。成为会员后就可以提出问题,多数问题都会很快得到熟悉情况的用户解答。
辅助程序与替代方案
是的,确实如此。有一些工作,其他程序确实比 ImageMagick 做得更好。通常是那些专门针对特定格式设计的程序,而不是像 ImageMagick 那样提供通用的图像处理功能。
- IrfanView 可能是 Windows 下最常见的图像查看器,也提供一些基本的图像处理功能。
- 像 Adobe Photoshop 或 Gimp 这样的 GUI 程序,更适合直接编辑和测试复杂的图像处理步骤。
- 对于数码照片 EXIF 头信息的处理,Jhead 和 ExifTool 比 ImageMagick 更加多样化。
- 从 PDF 中提取 JPEG 流应该用 xPDF 来完成。
- 视频处理最好用 VirtualDub,最理想的是与 AviSynth 及其编辑器 AvsP 搭配使用。
这并不是说在这类图像工作中就应该无视 ImageMagick。但把它们组合起来使用,你能做的事情会多得多。
使用 Cygwin
| 如上所述,ImageMagick 在设计时就以 UNIX 和 Linux 为目标平台,所以最简单的方法大概是在 Windows 系统上安装一个 Bash shell,然后运行已经为该系统编写好的各种 IM 脚本,例如 Fred Weinhaus 的脚本。Cygwin 正如其开发者所说,提供了一个"Windows 上的类 Linux 环境"。它由 Linux shell 中通常可用的所有工具组成。我在 Cygwin 的 Bash shell 下测试过 Fred Weinhaus 的一些 bash 脚本,发现它们功能完全正常。在 Cygwin 网站根页面的底部,你会找到一个标记为 Install or update now! 的链接,它会下载一个名为 Setup.exe 的安装存根。启动该程序后,它会提供一个站点镜像列表。选定其中一个后,该流程会提供一个树状视图,列出即将安装的工具。标准选项在我看来是合理的,所以你可以直接继续。安装流程随后会下载所需的软件包并在你的计算机上安装 Cygwin。 | 安装时请确保包含 "bc" 软件包,它默认未启用。可以从 Cygwin 安装面板中作为选项安装。这是一个浮点数学工具,在 IM 脚本中(例如 Fred Weinhaus 使用的那些)可能至关重要。 |
|---|---|
| 启动 Cygwin 时,其 Bash shell 看起来很像一个 DOS 窗口,即黑色背景的文本窗口。与 DOS 窗口一样,可以通过点击标题栏左侧窗口的系统菜单来选择字体。基本命令如下: |
- 使用
CD命令更改当前目录,与 DOS 中大致相同。但反斜杠 ("\") 必须替换为正斜杠 ("/")。驱动器也通过CD更改,与 DOS 下相同。因此CD w:将切换到驱动器 w:。与普通 Unix 环境不同,路径名不区分大小写。可以使用变音符号等特殊字符。对于某些命令,驱动器名需要以特殊语法传递,以避免使用冒号。例如/cygdrive/w/test。 - Cygwin 读取 Windows 的 PATH 环境变量,并相应地设置自己的 PATH。你可以在 Bash shell 中输入
echo $PATH来检查这一点。注意:与 Windows 不同,环境变量的名称区分大小写,所以引用 PATH 时必须使用大写字母。 - 与 Windows 不同,当前目录默认不在搜索路径中!例如,如果 shell 脚本 "autolevel1" 位于
W:\scripts,调用该 shell 脚本时不能仅仅切换到该目录。你必须在脚本前至少附加一个最简目录位置,像这样:./autolevel1(对于位于当前目录的脚本)。或者使用脚本的完整路径,像这样/cygdrive/w/scripts。 - 另外,你也可以使用
PATH=$PATH:/cygdrive/w/scripts将该脚本目录显式追加到搜索路径中。由于冒号被用作路径分隔符,你必须使用这种特殊写法,例如/cygdrive/w/scripts而不是w:/scripts。
本页面未来的版本中会扩展对 Cygwin shell 的这段说明。目前,以上信息应足以帮助你入门。
使用 DOS Shell 和批处理文件
转换脚本:从 UNIX Shell 到 Windows DOS
当直接从 DOS 命令行(使用 cmd.exe)调用 IM 命令时,你需要修改 IM 示例网站上展示的脚本(如果它们不是来自本节的话),因为(其他章节)提供的大多数示例通常是打算在 UNIX 或 LINUX 命令行中运行的。为了从 DOS 命令行运行它们,你需要进行以下修改:
- 大多数情况下,必须使用双引号 '
"' 代替单引号 ''',以使命令参数保持正确。要注意像[-draw](https://imagemagick.org/command-line-options/#draw)运算符中那样引号中套引号的情况。你可以在 DOS 双引号参数内使用单引号,因为这些会被传递给 IM 处理,而不是由脚本本身处理。 - 出现在示例行末尾的反斜杠 '
\' 表示"续行",会将下一行附加到同一命令行序列中。在 DOS 批处理文件中,应将它们替换为 '^' 字符来表示续行。对于 DOS,下一行也需要以空格开头,不过这是相当标准的做法,所以不应造成太大问题。你也可以把所有行拼接成一行并去掉那些反斜杠,不过这会让编辑、以及之后阅读该命令变得困难得多。因此不推荐这种做法。 - 所有未加双引号的保留 shell 字符,如果按字面意义使用(即不履行其通常用途),都必须用 '
^'(脱字符或 circumflex)转义。这些保留 shell 字符是:'&'、'|'、'('、')'、'<'、'>'、'^'。这尤其意味着:- 特殊字符 '
>'(用于调整大小的几何参数)需要用 '^' 转义。例如 "-resize 100x100^>" - 类似地,"内部适配缩放"标志 '
^' 需要加倍变成 '^^'。
- 特殊字符 '
- UNIX shell 的转义反斜杠 '
\' 并不需要用来转义括号 '()' 或感叹号 '!'。 - 除此之外,UNIX shell 的转义反斜杠 '
\' 需要替换为脱字符 '^',用于转义如 '<' 和 '>' 这类字符时尤其如此。
例如:"-resize 100x100\>" 会变成 "-resize 100x100^>"。 - 在 DOS 批处理文件中,百分号 '
%' 字符也具有特殊含义,因为它引用命令行参数。例如%1, %2, ...(在 UNIX shell 中同样的通用含义使用的是 '$' 符号)。在 DOS 批处理文件中,单个百分号(如在 "FOR命令"中出现的那样)需要加倍变成 '%%'。ImageMagick 本身通常只检查是否存在百分号,而不在意是否给出了不止一个。所以只要不是文本标签或注释字符串的一部分,把所有百分号都加倍通常不会有问题。 - 请记住,Windows 文件名可以包含空格字符。空格在 UNIX 下也可以使用,但不太常见。这类可能包含空格的文件名必须用双引号括起来,如
"file name.jpg"或"file name".jpg。通过拖放或"发送到"作为命令行参数传递给脚本或批处理文件的文件名需要特别注意,因为如果不含空格字符则不带引号传入,含空格时则带引号传入。 - UNIX shell 脚本中的注释以未加引号的 '
#' 开头(可出现在行中任意位置),并持续到行尾。颜色设置(例如表示红色的 "#FF0000")通常会被加引号以去除这种特殊含义。这种加引号在 DOS 下不是必需的,但在它们周围加双引号 '"' 也无妨,应予以保留。在 DOS 中,注释只能出现在行首,使用 'REM'、'@REM' 或 '::' 前缀。它们同样会持续到行尾。使用哪种注释方式由你选择。不过,在任何批处理文件中,良好的注释始终是推荐做法,这样几个月或几年后再回头看脚本时,你能知道每一步命令想要做什么。这也让别人更容易理解。所有脚本都应以一段较大的注释开头,说明该脚本的作用以及应如何使用。这只是良好的编程实践。 - 执行 DOS 批处理文件时,各条命令默认会被回显。也就是说,命令会显示在输出的 DOS 窗口中。而在 UNIX 下,你则需要添加特殊命令或选项才能以这种方式打印正在执行的命令。你可以通过在脚本开头加上 "
@ECHO OFF" 来关闭这种"回显"输出。UNIX shell 脚本中特殊的起始注释 "#!/path/to/shell" 在 DOS 批处理文件中不需要。因此,在 DOS 批处理文件中,这一行可以替换为 "@ECHO OFF" 命令。
例如,按照上述规则,这段 UNIX shell 脚本……
#!/bin/sh
# Create a negated rose image and overlay a comment
magick -background none -fill red -gravity center \
-font Candice -size 140x92 caption:'A Rose by any Name' \
\( rose: -negate -resize 200% \) +swap -composite \
output.gif
将会变成类似这样的 Windows DOS 批处理文件……
@ECHO OFF
:: Create a negated rose image and overlay a comment
magick -background none -fill red -gravity center ^
-font "C:\path\to the\font\candice.ttf" ^
-size 140x92 caption:"A Rose by any Name" ^
( rose: -negate -resize 200%% ) +swap -composite ^
C:\where\to\save\output.gif
我借助 SED(Streaming EDitor)写了一个基本的从 Linux shell 到 DOS 批处理文件的转换器。SED 是一个常见的 UNIX / Linux 文本文件处理程序,Windows 版本也可在此获取:http://sed.sourceforge.net/. SED 与 IM 类似,都是命令驱动的工具:IM 是命令驱动的图像处理程序,SED 则是命令驱动的编辑器。执行所需操作的 SED 脚本 cim.txt(去掉注释后)如下所示:
s/'/\"/g
s/%/%%/g
s/\\\([()!]\)/\1/g
s/\([&|<>]\)/^\1/g
s/^[ ]*#/::/
s/\(^.*\)\( #.*$\)/\2\n\1/
s/\(.:.*\.[a-z,A-Z]*\)[ ]/\"\1\" /g
s/\\[ ]*$/^/
s/^[ ][ ]//
你可以下载完整加注释版本的文件 sed_script.zip。如果将 SED 脚本 cim.txt 放在与要转换的 Linux shell 脚本相同的文件夹中,可以通过以下方式调用转换:
%programfiles%\GnuWin32\bin\SED -f cim.txt linux.scr > windows.bat
你也可以通过以下批处理文件,借助"发送到"或拖放来调用转换:
SET SP=%programfiles%\GnuWin32\bin
%SP%\SED -f %SP%\cim.txt "%~1"> "%~dpn1.bat"
这个批处理文件假定你已将 SED 脚本 cim.txt 放在 SED 的程序文件夹中。它以 Linux shell 脚本的文件名作为唯一命令行参数,并在同一文件夹中生成一个同名但扩展名为 '.bat' 的批处理文件。(那个神秘的文件名操作 "%~dpn1.bat" 将在下一节说明。)请注意:上述 SED 脚本只会执行上面提到的那些最基本的替换。它不会把复杂的 Linux shell 脚本(例如 Fred Weinhaus 网站 上展示的那些)转换成等价的批处理文件!
批处理文件中的文件名处理
如上所述,IM 在对图像文件应用一系列标准处理步骤时特别有用。在这种情况下,文件名会通过拖放或"发送到"作为命令行参数传递给脚本。使用这些技巧时,传给 DOS 批处理文件的文件名将是完全限定的文件名,即包含驱动器名和目录路径。你可以通过将文件拖放到以下批处理文件上来测试这一点:
ECHO Filename: %1
PAUSE
由于有 PAUSE 语句,DOS 窗口会一直保持打开,直到用户按下某个键,这样你就可以检查结果。用一个包含空格的文件名尝试上面的例子,你会注意到文件名会被双引号括起来。请注意,这里以及以下所有示例都假定任何网络文件都已被分配了一个驱动器字母。实际上,我在本地商用网络中从未见过其他情况。处理批处理文件时,不应尝试处理 UNC 名称:你或许能让批处理正常运行,但那会给你带来许多不必要的麻烦。在 Convert 命令行中使用这个文件名时,这种行为可能会导致问题。让我们执行一个从任意其他格式到 JPEG 的简单转换。最基本的代码会是这样:
magick %1 %1.jpg
PAUSE
这会在同一目录下生成一个 JPEG 文件(标准质量和分辨率),文件名末尾附加一个 ".jpg" 扩展名。上面的代码适用于任何文件名,无论其是否包含空格。如果想去掉原始扩展名,事情会稍微棘手一点:
magick %1 "%~dpn1.jpg"
PAUSE
上面的批处理文件通过使用 ~ 运算符来操作文件名: %~1 |
展开 %1,去除任何周围的引号 (") |
|---|---|
| %~f1 | 将 %1 展开为完全限定路径名 |
| %~d1 | 将 %1 展开为仅驱动器字母 |
| %~p1 | 将 %1 展开为仅路径 |
| %~n1 | 将 %1 展开为仅文件名 |
| %~x1 | 将 %1 展开为仅文件扩展名 |
这些修饰符可以组合使用,因此 "%~dpn1" 表示"驱动器 + 路径 + 不含扩展名和括起引号的文件名"。因此,我们必须用双引号将名称括起来,这样代码对包含空格的文件名也同样适用。(如果不这样做也不会造成问题,多加的引号无害。)PAUSE 语句仅用于测试目的,在最终的批处理文件中可以去掉。如果你只是想测试代码而不实际调用 IM,应该这样写: |
ECHO magick %1 "%~dpn1.jpg"
PAUSE
批量处理多个文件
FOR 循环
DOS 的 FOR 命令可以像在 UNIX 下一样,用来处理一系列文件。要将当前目录中所有 JPEG 文件缩放 50%,可以在 DOS 窗口中输入以下这一行:
FOR %a in (*.jpg) DO magick %a -resize 50% small_%a
请注意百分号没有加倍。但如果将该命令放入批处理文件中,则必须替换为
FOR %%a in (*.jpg) DO magick %%a -resize 50%% small_%%a
同样,通过拖放或"发送到"来调用这个批量操作也很方便,将完全限定的文件名(或文件夹名)传递给可能位于其他目录(例如 shell:sendto)中的 DOS 批处理文件。在这种情况下,我们应该在第一步就把文件所在目录设为当前目录:
%~d1
CD "%~p1"
MD small
FOR %%a in (*.jpg) DO magick %%a -resize 50%% small\%%a
PAUSE
在这个批处理文件中,我们
- 通过提供驱动器名(d: 或其他)来切换驱动器
- 将文件所在文件夹设为当前目录
- 创建一个名为 "small" 的子目录
- 将所有 JPEG 文件缩放 50%,并将这些缩小后的版本放入新的子目录中。
请注意:由于波浪号 (~) 会去掉作为命令行参数传入的文件名周围可能存在的引号,我们通常需要再次用引号将这类操作的结果括起来。因此我们在上面的例子中写的是 CD "%~p1"。对于 CD 命令,我们甚至可以省略括起来的引号,因为这条命令只接受一个参数,因此即使没有括起来的引号也能处理空格。在第一步就把文件所在目录设为当前目录,确实能让后续步骤更容易一些,因为对文件名的引用变得更简短、更容易。不过我们也可以这样写:
MD "%~dp1small"
FOR %%a in ("%~dp1*.jpg") DO magick "%%a" -resize 50%% "%%~dpasmall\%%~nxa"
PAUSE
这可能会让代码稍微短一点,但也会棘手得多。请注意,文件名修饰符对 FOR 循环变量 %%a 同样有效。注意二:末尾的反斜杠是路径名的一部分。因此必须写成 "%~dp1small" 而不是 "%~dp1\small",这并不能让代码更易读,尤其是在 "%%~dpasmall\%%~nxa" 这种情况下。FOR 语句有若干缺点和注意事项。其中之一是,DO 之后基本上只能执行一条命令。不过你可以用圆括号 "(", ")" 将一系列 DOS 命令分组,从而执行一个简单的命令序列:
@ECHO OFF
:: Lighten darker areas of all images in a directory
%~d1
CD "%~p1"
FOR %%a in (*.jpg) DO (
ECHO Processing file: "%%~nxa"
magick %%a -blur 30 -negate %%a.miff
magick composite %%a.miff %%a -compose overlay "%%~dpn1_light"%%~xa
DEL %%a.miff
)
PAUSE
这个批处理文件会处理作为命令行参数传入的目录中找到的所有图像。首先它会对原始图像进行模糊处理并反转,将中间结果存储在一个带有额外 ".miff" 扩展名的文件中。然后它将原始图像叠加在这个修改后的版本之上,从而使原始图像的暗部区域变亮。最后删除中间图像。请注意,上文中强调的重点在于简单的命令序列:你不能在这个块内使用 GOTO 跳转。如果你需要这样的行为,就必须通过 FOR 循环调用另一个批处理文件:
%~d1
CD %~p1
MD small
FOR %%a in (*.jpg) DO CALL "%~dp0process" "%%~fa"
其中 "process.bat" 是执行实际工作的批处理文件,它与调用它的批处理文件位于同一目录中。命令行参数 0("%0")是批处理文件自身的名称,因此 "%~dp0process" 会调用同一目录中的批处理文件 process.bat。FOR 语句只提供文件名,通过 "%~fa" 将其转换为完全限定的文件名。在本例中,批处理文件 process.bat 中的代码将与上面例子中放在圆括号之间的代码相同:
magick %%1 -blur 30 -negate %%1.miff
magick composite %%1.miff %%1 -compose overlay "%%~dpn1_light"%%~x1
DEL %%1.miff
不过,使用单独的批处理文件,可以获得 DOS 批处理文件(有限的)全部可能性。在本例中这并没有什么区别,但我们会在后面展示这种方法的优势。如果你不想为两个批处理文件而烦恼,可以让脚本创建第二个 process.bat 脚本(使用 ECHO),从主循环中调用它,工作完成后再删除它:
ECHO magick %%1 -blur 30 -negate %%1.miff >%~dp0process.bat
ECHO composite %%1.miff %%1 -compose overlay "%%~dpn1_light"%%~x1 >>%~dp0process.bat
ECHO DEL %%1.miff >>%~dp0process.bat
%~d1
CD %~p1
MD small
FOR %%a in (*.jpg) DO CALL "%~dp0process" "%%~fa"
DEL %~dp0process.bat
使用 ECHO 命令时,我们必须再次转义特殊的 DOS 字符,尤其是百分号。而且没错,IM 本可以用一条处理命令完成上述所有操作,从而无需 ".miff" 这个中间图像,但这不是本例要说明的重点。
批处理(子)目录树
有几种技术可以处理(子)目录树中的所有文件。最简单的方法是使用 "/R" 标志配合 FOR 语句,使其在当前目录下所有子目录中的所有文件上循环。要将子目录树中所有的 TIFF 文件 magick 为 JPEG,只需简单地输入:
FOR /R %%a IN (*.tif) DO imconv "%%~a" "%%~dpna.jpg"
使用 "/R" 标志时,你总是在遍历整个子目录树,没有对文件排序或过滤的选项。在接下来的示例中,我们将为所有子目录生成照片索引印样,并将其放在根目录下。这提供了一种简便的方式,对某张照片进行可视化搜索,类似于 Windows 资源管理器中的预览,但不需要每次搜索都(耗时地)重新扫描整个目录树。作为开始,我们借助两个批处理文件来解决这个问题,一个执行循环,另一个完成实际工作。索引印样将是低质量的 JPEG 文件,命名为 IDX_0001.jpg, IDX_0002.jpg, IDX_0003.jpg 等等。首先我们建立循环:
DEL IDX_????.JPG
SET COUNT=0
FOR /F "delims=" %%a in ('DIR /S /B /AD ^|FIND /I "Porsche" ^|SORT') DO CALL c.bat "%%a"
DEL title.txt
第一行清理之前搜索的结果。第二行中,我们定义环境变量 COUNT,用于生成 IDX_nnnn.JPG 文件名。第三行中,我们通过 DIR /S /B /AD 建立所有子目录的列表,提取包含 "Porsche" 一词的目录(通过选项 /I 不区分大小写),并对这个过滤后的列表进行排序。排序将确保 IDX 文件的数字顺序与目录路径名的字母顺序一致。选项 "delims=" 会禁止在第一个空格后截断行的标准行为。调用批处理文件 C.BAT 时,我们用引号将路径名括起来,以确保空格被正确处理。最后一行,我们删除批处理文件 C.BAT 创建的临时文件。现在我们来看实际的工作:
CHCP 1252
DIR %1\*.jpg>nul || GOTO :EOF
:: Generate IDX filename
SET /A COUNT+=1
SET TFILE=000%COUNT%
SET TFILE=IDX_%TFILE:~-4%.jpg
:: Generate title without bracketing quotes
ECHO %~1>temp.txt
"C:\Program Files\Gnuwin32\bin\iconv.exe" -f ISO-8859-1 -t UTF-8 temp.txt>title.txt
montage -geometry 210x140+0+5 -tile 6x -title @title.txt %1\*.jpg -quality 30%% %TFILE%
jhead -cl %1 %TFILE%
第一行我们将代码页切换为西欧拉丁语 (ISO-8859-1)。第二行,我们检查该目录中是否确实包含照片,如果没有则跳过批处理的其余部分。(即使执行其余部分实际上也无害,因为 magick montage 只是不会产生任何输出,但 IDX 文件的编号就不再连续了。)从 Windows XP 开始有一个特殊的 GoTo 目标标签 :EOF,它允许在不定义相应标签的情况下终止执行。然后我们生成索引文件的文件名 "TFILE":递增 "COUNT",附加一些前导零,并通过 %TFILE:~-4% 提取末尾 4 个字符,再拼接成 "IDX_nnnn.jpg" 形式的文件名。使用 SET /A 语句进行计算的方法将在后面的 使用 SET 进行计算 中说明。接下来的几行中,我们将路径名 "PNAME" 去掉引号,并将结果存储在中间文件 'temp.txt' 中,借助 "Icon.exe" 将其转码为 Unicode (UTF-8)(参见 "字符编码")。存储在 'title.txt' 中的 Unicode 字符串随后由 IM 的 Montage 读取。这确保了该字符串会被按字面处理,因此我们不必对 Windows 路径名中的反斜杠进行转义。Montage 随后将照片按每行六张排列(-tile 6x),并以路径名作为标题。生成的索引印样宽度为 1260 像素,并以 30% 的 JPG 质量存储,以减少存储需求。最后一行,我们使用程序 JHEAD 将路径名写入 JPEG 注释中。这样就可以在 Windows 资源管理器中,通过对文件名中的特定子字符串进行文本搜索来过滤索引印样。我们可以把这两个批处理文件合并,把"工作批处理"的代码放进 FOR 循环中:
SETLOCAL EnableDelayedExpansion
SET ICONV="%PROGRAMFILES%\Gnuwin32\bin\iconv.exe" -f ISO-8859-1 -t UTF-8
CHCP 1252
DEL IDX_????.JPG
SET COUNT=0
FOR /F "delims=" %%a in ('DIR /S /B /AD ^|FIND /I "Porsche" ^|SORT') DO (
DIR "%%a\*.jpg">nul
IF !ERRORLEVEL!==0 (
SET /A COUNT+=1
SET TFILE=000!COUNT!
SET TFILE=IDX_!TFILE:~-4!.jpg
ECHO %%a >temp.txt
%ICONV% temp.txt>title.txt
montage -geometry 210x140+0+5 -tile 6x -title @title.txt "%%a\*.jpg" -quality 30%% !TFILE!
jhead -cl %%a !TFILE!
)
)
DEL temp.txt
DEL title.txt
基本上,需要对最初的代码做两处修改:
- 我们必须启用延迟展开,并在
FOR循环内引用所用的环境变量时用感叹号而不是百分号将其括起来。 - 我们必须避免使用会重置命令处理器的
GOTO语句。
默认情况下,FOR 循环内的环境变量不会在运行时求值。相反,代码是使用括号中给出的列表预先处理的。因此,对 %COUNT% 在 FOR 循环内的引用总是返回相同的值。为了启用环境变量的运行时求值,你必须打开延迟展开。可以通过 cmd /V:on 调用命令处理器来实现,也可以使用以下 REG 文件在注册表中全局打开:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor]
"DelayedExpansion"="1"
[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]
"DelayedExpansion"="1"
不过,更简便可靠的做法是在批处理文件本身中使用 SETLOCAL EnableDelayedExpansion 来设置该选项,这样也会把对环境变量的任何更改限制在当前版本的命令处理器中,这可能正是所需要的。循环内对环境变量运行时值的引用现在必须使用感叹号而不是百分号。GOTO 语句在 FOR 循环内使用时可能成为非常隐蔽的错误来源,因此应当避免。不过在我们的批处理中,可以很容易地用 IF 语句把包含 montage 代码的代码块括起来,从而替代跳转。
批处理任意数量的文件
在 DOS 批处理文件中,只有九个命令行参数可以通过 %1 到 %9 直接寻址。在早期的 Windows 版本中,唯一能绕过这一限制的方法是 SHIFT 命令,它会使命令行参数循环移位。在较新的版本中,命令行参数可以在 FOR 循环中处理:
FOR %%i in (%*) DO ...
这使我们可以对拖放传入的任意数量的图像进行 Montage:
@ECHO OFF
SETLOCAL EnableDelayedExpansion
%~d1
CD "%~p1"
DEL Files.txt 2>nul:
DEL FSorted.txt 2>nul:
FOR %%I in (%*) DO ECHO %%~nxI>>files.txt
FOR /F "delims=" %%A in ('TYPE files.txt ^| Sort') Do (
ECHO %%A>>fsorted.txt
)
SET MONTAGE=montage -tile 3x
FOR /F "delims=" %%A in (FSorted.txt) Do (
SET MONTAGE=!MONTAGE! %%A
)
SET MONTAGE=%MONTAGE% result%~x1
%MONTAGE%
DEL Files.txt
DEL FSorted.txt
上述代码假定作为命令行参数传入的文件位于同一目录,并且需要按文件名的字母数字顺序进行 montage 处理。举例来说,这对于数码照片的索引很有意义,因为这样就能按拍摄时间排序。我们首先将文件所在目录设为当前目录,这会让后面的代码简单很多。然后我们将文件名转存到 files.txt 中。即使这些文件是按字母顺序选取的,也不能保证在将文件名传递给脚本时,这个顺序会被保留。因此,我们在第二个工作步骤中对文件进行排序,转存到 fsorted.txt 中。基于该文件,我们再在另一个 FOR 循环中构建 Montage 命令行。Montage 的输出文件使用与第一个输入文件相同的扩展名 (%~x1)。(假设所有文件都共享相同的扩展名。)请注意,最终的 Montage 命令只是通过对环境变量求值来调用的,即 %MONTAGE%。正如 前面 所说,在 Windows XP 下传入大量长(完整)文件名的文件可能会带来麻烦,因为 ShellExecute 的参数列表限制为 2048 个字符。这个错误无法由批处理文件处理,因为它发生在控制权交给批处理文件之前。一种可行的基于脚本的解决方案是:当只向批处理传入一个文件时,处理该目录中所有的图像文件:
%~d1 & IF EXIST %1\* (CD %1) ELSE CD %~p1
IF "%2"=="" (
SET PATTERN=*.jpg
) ELSE (
SET PATTERN=%*
)
FOR %%v IN (%PATTERN%) DO (...)
第一行中我们检查第二个命令行参数是否存在。如果存在,则处理所有命令行参数 (%*)。如果只向脚本传入了一个文件,则将模式设置为所有 JPEG 文件。这种简单的模式匹配要求我们已经切换到了当前驱动器(通过 %~d1)和当前文件夹,即
- 如果传给脚本的是文件夹名,则 CD %1
- 如果传给脚本的是文件名,则 CD %p1
从 ImageMagick 获取信息
复用 IM 命令的输出
在较新的 Windows 版本中,FOR 语句变得更加强大,参见 DOS "For" Command Help。通过使用 "/F" 选项,你可以从文件、字符串,或者另一个 DOS 命令或程序的输出中读取替换变量的输入。后者对 IM 特别有用。要粗略了解 IM 的叠加方式到底是怎么回事,可以使用下面的批处理文件:
@ECHO OFF
:: compose two gradients using all compose methods available
::
magick -size 80x80 -flip gradient: compose_src.png
magick compose_src.png -rotate 90 compose_dst.png
FOR /F %%A in ('magick -list compose') DO ^
magick composite compose_src.png compose_dst.png -compose %%A compose_%%A.png
![[IM Output]](../static/img/images/compose_src.png)
Src | ![[IM Output]](../static/img/images/compose_dst.png)
Dst | | ![[IM Output]](../static/img/images/compose_multiply.png)
Multiply | ![[IM Output]](../static/img/images/compose_screen.png)
Screen | ![[IM Output]](../static/img/images/compose_overlay.png)
Overlay
---|---|---|---|---|---
这个脚本使用所有可用的 Alpha 合成方式 将两张渐变图像合成在一起,这样你就可以看到运算符对图像颜色的影响。上面只展示了它生成的部分图像。这与为 Composition Tables 生成的图像类似,同样可以看出运算符对图像颜色的影响。由于以上几行被当作批处理文件,我们必须把百分号写成两个。上述脚本首先创建两张正交(成直角对齐)的灰阶图像,其渐变覆盖整个灰阶范围。IM 命令 Convert -list compose 会给我们提供一份可用选项列表,每个选项各占一个输出行。请注意,在括号中引用命令时必须使用单引号。使用 "/F" 选项后,FOR 命令会处理这些输出行中的每一行,并将其传递给由 DO 执行的命令。因此,这两张渐变图像会应用 IM 已知的所有叠加方式进行叠加。输出文件会以相应的叠加方式命名。 在下一个例子中,我们展示 IM 提供的色彩空间。我们使用与上面相同的渐变技巧,生成由色彩空间三个坐标所张成的立方体各个面:
magick -size 256x256 gradient: gy.miff
magick gy.miff -rotate 90 gx.miff
magick -size 256x256 xc:black black.miff
::R + G top left
magick gy.miff gx.miff -flop black.miff -set colorspace %1 -combine ^
-resize 260x300! -background none -shear 0x-30 ^
-virtual-pixel Transparent RG.miff
:: R + B top right
magick gy.miff black.miff gx.miff -set colorspace %1 -combine ^
-resize 260x300! -background none -shear 0x30 RB.miff
:: G + B bottom
magick black.miff gx.miff gy.miff -set colorspace %1 -combine ^
-resize 260x300! -background none -shear 0x30 -rotate 120 ^
-crop 520x300+0+75 GB.miff
magick -set colorspace %1 RG.miff RB.miff +append top.miff
magick -set colorspace %1 -size 520x150 xc:Transparent w.miff
magick top.miff w.miff -append topx.miff
composite -geometry +0+299 GB.miff topx.miff colorspace_%1.png
DEL *.miff
![[IM Output]](../static/img/images/colorspace_RGB.png)
colorspace RGB | ![[IM Output]](../static/img/images/colorspace_sRGB.png)
colorspace sRGB
---|---
上面这个使用 UNIX shell 脚本的类似例子可以在 使用切变实现的等距立方体 中找到。
这个批处理文件把色彩空间作为命令行参数 "%1"。然后它生成立方体的三个面,并对其进行切变和拼接,从而得到一个等距视图,其中点 (0,0,0) 位于六边形的中心。最终图片以色彩空间命名(即 "%1"),并保存为 PNG。现在我们想从另一个提供色彩空间名称的批处理文件中,调用这个(保存为 "cspace.bat" 的)批处理文件:
FOR /F %%A in ('magick -list colorspace') DO CALL cspace %%A
我们也可以在 DOS 中通过管道过滤 -list 选项的输出:
magick -list colorspace | FIND "RGB" >>clist.txt
FOR /F %%A in (clist.txt) DO CALL cspace %%A
DEL clist.txt
在这个例子中,我们从输出中过滤出包含 "RGB" 的行,并写入文件 clist.txt。然后这个文件被用作 FOR /F 命令的输入。我们也可以一次性完成,避免使用临时文件:
FOR /F %%A in ('magick -list colorspace ^| FIND "RGB"') DO CALL cspace %%A
在这种情况下,管道符号 "|" 必须被转义,因为它没有被双引号括起来(只被 FOR 语句所需的单引号括起来),并且(至少在上面这条命令行中)不是按其通常含义使用的。
处理单行输出
这种技术也可以有效地应用于单行输出。例如,按照 Fred Weinhaus 的网站 上说明的技术,我们可以应用一种自动伽马校正,把图片的平均亮度大致设置到量子范围的中点(即色深为 8 位时为 127):
FOR /F %%a in ('identify -format "%%[fx:log(mean)/log(0.5)]" %1') DO ^
magick %1 -gamma %%a "%~dpn1"_c.%~x1
这个批处理文件接收一个完整限定的文件名作为命令行参数 "%1",多半是通过拖放或 SendTo 传入的。IM 的 Identify 命令的输出随后提供一个伽马值,用于把图像的平均亮度设置到其动态范围的中点。这个值是使用 FX 格式表达式 计算得到的。Identify 命令的单行输出被保存到 "%%a" 变量中,并作为 伽马运算符 的参数传递给 Convert 命令。 |
FOR 命令在行延续方面似乎相当敏感:如果你要使用行延续,请确保下一行不要以空格开头。 |
|---|---|
这种自动伽马校正的方法现在已内置到 IM 中,即"[-auto-gamma](https://imagemagick.org/command-line-options/#auto-gamma)",是在 IM v6.5.5-1 中新增的。但它展示了在后续命令参数中复用命令输出的技术。 |
|
| --- | --- |
利用同样的 FOR 技术,我们可以读取照片中嵌入的 EXIF 信息,并将其写入图像的左上角: |
FOR /F "tokens=1,2" %%i IN ('identify -format "%%[EXIF:DateTime]" %1') DO ^
magick %1 -pointsize 18 -fill white -gravity northwest ^
-annotate +0+0 "%%i %%j" "%~dpn1"_dated%~x1
| 照片通常以 JPEG 格式保存。由于JPEG 有损压缩,读取并重新保存 JPEG 图像会导致图像轻微劣化,因此不建议再保存回 JPEG。
---|---
在上面的批处理文件中,照片的文件名作为唯一的命令行参数传入,被称为 "%1"。Identify 命令从 JPEG 文件内的 EXIF 信息中读取拍摄该照片的日期和时间。FOR 命令随后把这个输出传给 Convert,由它在照片左上角添加相应的注释。EXIF 的日期时间信息格式为 "yyyy:mm:dd hh:mm:ss",例如 "2006:12:26 00:22:38"。因此日期和时间由一个空格字符分隔。默认情况下,FOR 语句只会找到每行中的第一个 token("单词"),以制表符和空格字符作为标准分隔符。因此在上面的例子中,标准处理只会返回日期,而不会返回时间。使用选项 "tokens=1,2",我们声明对这两个 token 都感兴趣,它们会被依次命名,即 "%x, %y"。不过,我们可以通过以下代码改变日期这种相当非常规的格式:
FOR /F "tokens=1,2,3,* delims=: " %%i IN ('identify -format "%%[EXIF:DateTime]" %1') DO ^
magick %1 -pointsize 18 -fill white -gravity northwest ^
-annotate +0+0 "%%j/%%k/%%i %%l" "%~dpn1"_dated%~x1
现在我们把冒号 (':') 定义为一个额外的分隔符,使日期被拆分为三个 token "%i"、"%j"、"%k"。接下来找到的分隔符是分隔日期和时间的空格字符。用星号 ("*") 我们要求把该行的其余部分存入第四个 token "%l"。现在我们就可以按自己喜欢的方式格式化日期了。如上例所示,我们选择了英美式表示法 "mm/dd/yyyy"。
从单个命令设置多个值
从单个 ImageMagick 命令设置多个值的一种技巧是让该命令对数据进行格式化,从而可以设置多个变量。
FOR /F %%L IN ('identify -format "Width=%%w\nHeight=%%h" %1') DO set %%L
这会通过一次 ImageMagick 命令调用,同时设置 DOS 变量 'Width' 和 'Height'。
执行计算
DOS 命令解释器在计算方面表现不佳。你可以用它来执行简单的整数运算。但要进行更复杂的浮点数学运算,你可以使用 IM 的 FX 格式表达式,或者第三方的 DOS 计算程序。
使用 IM 的 FX 表达式
IM 的 FX 格式表达式 可用于浮点数学运算,并可以把这种运算结果添加到更大的格式化字符串中,正如上文 处理单行输出 一节的第一个示例所演示的那样。通过使用 SET 命令,可以把结果存入环境变量,并在批处理文件的后续部分使用。举一个简单的例子,我们可能希望让上面示例中的日期时间字符串的字体大小,随照片的尺寸而调整:
FOR /F %%i IN ('identify -format "%%[fx:min(w,h)*0.05]" %1') DO SET psize=%%i
FOR /F "tokens=1,2,3,* delims=: " %%i IN ('identify -format "%%[EXIF:DateTime]" %1') DO ^
magick %1 -pointsize %psize% -fill white -gravity northwest ^
-annotate +0+0 "%%j/%%k/%%i %%l" "%~dpn1"_dated%~x1
第一行中,我们通过 "%[fx:min(w,h)]" 计算出照片较小一侧的尺寸,取该值的 5% 并存入环境变量 PSIZE。这个值会在下一条语句中被引用(%psize%),用于设置日期时间信息的字体大小。接下来,我们计算一个 -15° 到 +15° 之间的随机角度(整数),用来生成一张旋转后的缩略图。
FOR /F %%x IN ('magick null: -format "%%[fx:int(rand()*31)-15]" info:') DO SET angle=%%x
magick %1 -thumbnail x90 -alpha set ^
-background none -rotate %angle% "%~dpn1"_rotated.png
FX 表达式 不仅能生成单个数字,还能生成嵌入在更大字符串中的多个数字。例如在 圆角边框 一节中,它就被用来根据图像的宽高信息,直接生成一段复杂的 draw 字符串。这个附加特性,加上能够避免对其他外部程序的进一步依赖,使得这种方法成为在批处理脚本中进行计算的更优选择。
使用 SET 命令
SET 命令在调用 "/A" 选项时,可以执行一些简单的整数运算和一些基本的字符串操作。在下面的例子中,我们通过 SET 命令粗略计算日期时间字符串的宽度:
:: Determine the font height
FOR /F %%i IN ('identify -format "%%[fx:min(w,h)*0.05]" %1') DO SET psize=%%i
:: The width of the date-time string is roughly 9 times its height
SET /A pwidth=%psize% * 9
:: Calculate the average brightness in this section
:: and choose the text color correspondingly
FOR /F %%i IN ('identify -format "%%[fx:mean]" -crop %pwidth%x%psize%+0+0 %1') DO SET mean=%%i
IF %mean% LEQ 0.5 (SET fcolor=white) ELSE SET fcolor=black
:: Annotate the photograph
FOR /F "tokens=1,2,3,* delims=: " %%i IN ('identify -format "%%[EXIF:DateTime]" %1') DO ^
magick %1 -pointsize %psize% -fill %fcolor% -gravity northwest ^
-annotate +0+0 "%%j/%%k/%%i %%l" "%~dpn1"_dated%~x1
这个示例批处理文件根据日期时间字符串将要放置区域的平均亮度(%mean%),来选择该字符串的颜色。如果平均颜色强度低于 50%,字符串就是白色,否则就是黑色。这个例子还用到了 IF 语句。请注意,ELSE 部分必须放在同一行,并且第一条命令必须用括号括起来。
使用其他外部计算程序
作为替代方案,你可以使用一个提供浮点运算的 DOS 程序,例如 EVAL。如果把这个文件放到 IM 的程序目录或 Windows 系统目录中,你就可以在任意 DOS shell 窗口里进行浮点计算。通过使用 EVAL 程序、FOR 命令和环境变量,我们可以让上面的色彩立方体示例变得更灵活一些,并让其中各种计算更加清晰易懂:
magick -size 256x256 gradient: gy.gif
magick gy.gif -rotate 90 gx.gif
magick -size 256x256 xc:black black.gif
:: Set the dimension of the color cube / hexagon and calculate the various lengths
SET l1=512
FOR /F %%i IN ('EVAL "round(cos(degree*30)*%l1%)"') DO SET l2=%%i
FOR /F %%i IN ('EVAL "2*%l2%"') DO SET l3=%%i
FOR /F %%i IN ('EVAL "round((1+sin(degree*30))*%l1%/2)"') DO SET l4=%%i
FOR /F %%i IN ('EVAL "round(%l1%/4)"') DO SET l5=%%i
magick gy.gif gx.gif -flop black.gif -set colorspace %1 -combine ^
-resize %l2%x%l1%! -background none -shear 0x-30 ^
-virtual-pixel Transparent RG.miff
magick gy.gif black.gif gx.gif -set colorspace %1 -combine ^
-resize %l2%x%l1%! -background none -shear 0x30 RB.miff
magick black.gif gx.gif gy.gif -set colorspace %1 -combine ^
-resize %l2%x%l1%! -background none -shear 0x30 -rotate 120 ^
-crop %l3%x%l1%+0+%l5% GB.miff
magick -set colorspace %1 RG.miff RB.miff +append top.miff
magick -set colorspace %1 -size %l3%x%l4% xc:Transparent w.miff
magick top.miff w.miff -append topx.miff
magick composite -geometry +0+%l1% GB.miff topx.miff colorspace_%1.png
DEL *.miff
DEL *.gif
编辑、调试与运行时错误测试
原则上,DOS 批处理文件可以用任何编辑器编写,甚至用 Windows 的记事本也行。不过你应该使用一款对 DOS 批处理文件提供语法高亮的编辑器。我个人认为 Notepad++ 是首选工具,但谈论编辑器往往容易惹人不快。所以:好吧,其他任何编辑器也都可以。据我所知,市面上没有免费的批处理文件 IDE(集成开发环境)。人们可能会觉得这应该是操作系统自带的东西,但事实从来都不是如此。到目前为止,我所有的批处理文件都是用 Notepad++ 写的,但对于经常编写批处理文件的人来说,Running Steps 这款批处理 IDE 或许会有帮助。它是共享软件,售价约 80 美元。DOS 命令的详尽说明可参见 http://www.computerhope.com/msdos.htm. 和 DOS 批处理语言本身一样,调试批处理文件也是件相当古怪的事。我会先在 DOS 窗口里测试任何批处理文件。在测试拖放(Drag & Drop)或“发送到”(SendTo)功能时,建议在批处理文件末尾加上一条 PAUSE 语句,这样批处理任务结束后 DOS 窗口会保持打开。关于运行时错误消息,通常的做法是检查 DOS 的 ERRORLEVEL,并跳转到由 ECHO 命令生成的相应错误消息处。我发现最可能出现错误的原因之一,就是在运行脚本的机器上没能正确找到 Convert 程序。因此,如果你打算把批处理脚本分享给别人,首先应该检查一下 Convert 是否可以访问:
@ECHO OFF
magick -version 1>nul: 2>nul:
IF NOT %errorlevel%==0 GOTO NoMagick:
magick ...
GOTO :EOF
...
:NoMagick
ECHO ImageMagick (convert.exe) not found.
PAUSE
在第一行中,我们查询 ImageMagick 的版本,用 1>nul:(或者只是 >nul:)抑制标准输出,用 2>nul: 抑制任何错误消息,也就是把 _stdout_ 和 _stderr_ 都重定向到 nul:。如果对 IM 的 Convert 的调用失败,系统自带的 Convert 程序就会被改为调用,而它无法处理 -version 选项,会设置 ERRORLEVEL 变量。你也可以尝试查明为什么找不到 Convert,并试着解决这个问题:你可以判断 IM 的程序路径是否在环境变量 PATH 中:
@ECHO OFF
PATH | FIND /I "ImageMagick"
IF NOT %errorlevel%==0 GOTO NoPath:
...
:NoPath
...
如果 Find(以不区分大小写的选项 /I 调用)找不到该字符串,就会设置 ERRORLEVEL。用更精细一点的方法,你可以改为检查注册表项,而不再依赖 PATH:
@ECHO OFF
FOR /F "tokens=1,2,*" %%A in ^
('reg query "HKCM\Software\ImageMagick\Current" ^| FIND "BinPath"') DO ^
SET MPATH=%%C
IF [%MPATH%]==[] GOTO NoMagick:
"MPATH\convert.exe" ...
...
:NoMagick
...
通过这段代码,我们查询 IM 的注册表键 Current,并搜索条目 BinPath。输出中起决定作用的那一行是:LibPath REG_SZ C:\Program Files\ImageMagick这一行文本中的“单词”由制表符(在 Windows XP 中)或若干空格(Windows Vista)分隔。这些正是 For /F 所使用的标准分隔符。第三个“单词”(%%C) 就是我们要找的内容,我们把它存入环境变量 MPATH,之后在脚本中调用 magick 时可以引用它。某个脚本可能要求安装的 ImageMagick 达到某个最低版本号。例如,透视变形方法 是在 6.3.5-9 版本(2007 年 9 月)中首次实现的。因此,如果你的脚本涉及透视校正,就应该测试一下已安装的 IM 版本是否比它更新:
@ECHO OFF
SETLOCAL EnableDelayedExpansion
SET MINVERSION=7.5.3-0
FOR /F "tokens=1,2,3" %%a in ('magick -version ^|FIND "Version"') DO SET VERSION=%%c
IF %VERSION% LSS %MINVERSION% GOTO GetNewVersion:
Goto :EOF
:GetNewVersion
ECHO This Script requires et least ImageMagick version %MINVERSION%.
ECHO Yours is %VERSION%.
PAUSE
Goto :EOF
SETLOCAL 把对环境变量的任何更改限制在当前脚本范围内,这样我们就不必担心副作用。选项 EnableDelayedExpansion 在这里其实并非必需,但每次使用 SETLOCAL 时都养成使用该选项的习惯是个好做法。接下来我们把所需的最低版本存入环境变量 MINVERSION。第三行中,我们用 '-version' 选项调用 Convert,通过 ^|FIND "Version" 从输出中提取第一行,取该行的第三个单词并存入环境变量 VERSION。然后我们在第四行把这个版本与所需的最低版本进行比较。
优化执行时间
要测量执行时间,你可以显示环境变量 %TEMP% 的内容。下面的脚本测试了几种不同的方法,用来计算一个(较大的)图像文件——比如一张数码照片——的平均亮度:
IF "%1"=="" GOTO :EOF
ECHO %TIME%
Identify -verbose %1 | FIND "mean" & ECHO %TIME%
copy %1 %TEMP%\*.* & ECHO %TIME%
Identify -verbose %TEMP%\%1 | FIND "mean" & ECHO %TIME%
Convert %TEMP%\%1 -format %%[fx:mean] info: & ECHO %TIME%
Convert %TEMP%\%1 -resize 1x1! -format %%[fx:mean] info: & ECHO %TIME%
DEL %TEMP%\%1
图像作为唯一的命令行参数传给脚本,并在脚本第一行进行检验。接下来,通过在每条语句前后回显环境变量 %TIME%,来测量各种方法的执行时间。& 符号可以把多条 DOS 命令放在同一行,这里只是用它来缩短代码所占的篇幅。如果图像位于网络驱动器上,传输到客户端计算机内存中可能会消耗相当一部分执行时间。因此,文件会被复制到本地临时文件夹中,该文件夹的名称保存在环境变量 %TEMP% 中。结果表明,简单的 Convert 命令所耗费的执行时间,与过滤 Identify 的结果几乎一样多,但通过 -resize 1x1! 把图像缩放为一个像素,能显著加快操作速度,而不会让结果发生太大变化。请注意,这里我们使用的是 ECHO %TIME% 而不是 TIME /T,因为后者只会显示时和分,而前者会提供精确到百分之一秒的时间。与 UNIX 命令 shell 不同,这里没有直接的方法来测量相对时间,也就是设定一个参照点、执行语句,然后评估所耗费的时间。批处理文件内相对时间的计算之所以困难,是因为批处理文件只允许整数运算。可以用 FOR 命令提取秒数,然后用 IM 的 fx 运算符执行减法,并允许 60 秒的溢出
FOR /F "tokens=1,2,* delims=:" %%a in ("%TIME%") DO SET START=%%c
... some command(s)...
FOR /F "tokens=1,2,* delims=:" %%a in ("%TIME%") DO SET STOP=%%c
Convert null: -format "%%[fx:(%STOP%-%START%<0.0)?%STOP%-%START%+60.0:%STOP%-%START%]" info:
不过这只在小数分隔符被设为小数点的 Windows 版本上有效,因为时间是按照区域设置提供的,而 fx 是按照美式方案进行计算的。与 VBScript 不同,在批处理文件内没有办法更改区域设置(除了通过注册表为整台机器修改)。作为替代方案,可以把整个时间 magick 成百分之一秒,这样可以用整数运算完成,之后计算差值时同样也是整数运算。基本代码如下
for /f "tokens=1-4 delims=:., " %%a in ("%TIME%") do ^
set /a Start100S=1%%a * 360000 + 1%%b * 6000 + 1%%c * 100 + 1%%d - 36610100
在拆分出的时间代码每一部分前面插入的 "1",是为了防止带前导零的数字被(错误地)解释为八进制数。由此产生的“计算误差”,会在最后通过减去 36610100 得到补偿。完整的(也更优雅的)示例代码可以在这里找到。另一种方法是 Windows 2003 资源工具包提供的 TimeIt 实用工具,可以在这里下载。不过官方只支持 Windows XP。
批处理编程指南
简而言之,编写批处理文件时应遵循以下规则:
- 以
SETLOCAL EnableDelayedExpansion开头 - 定义一个变量来引用 IM 的 Convert 工具:
SET IMCONV="%PROGRAMFILES%\ImageMagick" - 处理多个文件时,把包含这些文件的文件夹设为当前文件夹通常会简化代码,即
- 通过
%~d1切换驱动器 - 通过
CD %~p1切换文件夹
- 通过
- 使用 GnuTools 的 iconv.exe 把文本 magick 为 UTF-8,并从文件读取文本。
- 记住 Convert 和 Montage 会把文本输出写到 stderr,而不是 stdout。
- 很多错误都源于包含空格的文件名,所以要检查每个批处理是否能正确处理这类文件名。
- 别忘了把每个百分号都写两遍,比如设置 JPEG 质量时。
- 别忘了对字符串中的特殊字符进行转义,例如 "^|"。
SET /A执行的整数计算会把任何以零开头的数字当作八进制数处理,因此要在每个这样的数字前加上 "1"。
小结
上面的例子证明,简单的 DOS 批处理文件在与 ImageMagick 所提供的各种可能性相结合时,展现出惊人的多面性。事实上,几乎任何事情都可以在批处理文件中以某种(略显粗糙的)方式完成。一旦你习惯了 DOS 批处理文件语言在开发过程中所遵循的那些奇特思路,脚本甚至可以写得相当简短。尽管如此,除非你真的对批处理文件语言很熟悉,否则这寥寥几行代码大概率也已经耗费了你数小时枯燥的试验。如果你的目标不止是基本的图像处理任务,那么使用更成熟的脚本语言大概会更值得推荐,因为那样代码的开发会变得更简单、更有条理。
Visual Basic Script (VBS)
Microsoft Windows Script Host(WSH)的脚本功能比简单的批处理文件语言更为完善。WSH 在可以利用不同 Active Scripting 语言引擎这个意义上是与语言无关的。默认情况下,它会解释并运行纯文本的 JScript 文件(Java Script)和 VBScript 文件(VisualBasic Script)。Windows Script Host 在 Windows 98 及更高版本中默认分发并安装(不过出于安全考虑,目标机器上可能已被关闭)。WSH 实现了一个对象模型,公开了一组 COM 接口,可用来访问系统对象,尤其是文件系统。这里不会详细讨论 Windows Script Host(其他地方已有更好的介绍),而是给出一些针对典型问题的实用示例。示例采用 VisualBasic Script 编写,但 JScript 代码会非常相似,因此如果你更喜欢 JScript,把示例改写成 JScript 应该很容易。与批处理文件一样,VBScript 可以用任何编辑器编写,这里同样推荐使用 Notepad++ 作为首选编辑器。和批处理文件一样,Microsoft 没有提供专门为支持 VBScript 开发而定制的 IDE。曾经有一个随 Microsoft Office 2000 到 2003 一起发布的 Microsoft Script Editor,但我从未试用过。Microsoft 还提供了(非常简陋的)Microsoft Script Debugger,同样,我对它也没有太多个人经验。有几款商业 VBS IDE 以合理的价格作为共享软件提供,例如 VbsEdit。正如 在 Windows 下安装 ImageMagick 一节中所说,下面我们通常不会使用 ImageMagick 的 COM+ 接口。Convert、Montage 和 Identify 等 IM 工具将改为通过调用 shell 的 run 命令,附带所有需要的选项和文件名,直接运行,这与批处理文件中的做法相当类似。然而,在组装命令字符串时,我们会利用真正的编程语言所提供的特性。
一个基本示例:镜头校正
由于使用 WSH 会产生一些额外开销,我们的起始示例不会太基础,以便展示 VBScript 相较于简单批处理文件的优势。下面我们将使用 IM 的桶形畸变来校正 Nikon 995 数码相机的镜头畸变。校正参数取决于焦距,而焦距首先通过 magick identify 查询得到。对于 Nikon 995 镜头的校正,我们只需要参数 b(即 a, c = 0),可以由焦距 f 通过下式计算得出:b = 0.000005142 f ³ - 0.000380839 f ² + 0.009606325 f - 0.075316854 这一依赖关系是借助 lensfun 数据库 找到的,该数据库列出了这款镜头的桶形畸变参数。于是这就是我们的 VBScript:
SetLocale(1033) ' US, i.e. decimal point
const strConv = "IMCONV" ' name of the IM Convert program
const strAdd = "_pt" ' string attached to the filename
'
Dim wsh
Set wsh = CreateObject("Wscript.Shell")
'
' names of the in- and output files
strFileIn = WScript.Arguments(0)
Pos = InStrRev(strFileIn,".")
strFileOut = Left(strFileIn,Pos - 1) & strAdd & Mid(strFileIn, Pos)
'
' evaluation of the focal length and calculation of parameter b
command = "identify -format ""%[EXIF:FocalLength]"" """ & strFileIn & """"
Set objExec = wsh.Exec(command)
strf = objExec.StdOut.Readline
f = eval(strf)
b = 0.000005142 * f * f * f -0.000380839 * f * f + 0.009606325 * f -0.075316854
'
Command = strConv & " """ & strFileIn _
& """ -virtual-pixel black -filter point -distort Barrel ""0.0 " _
& b & " 0.0 "" """ & strFileOut & """"
wsh.run command,7,true
在第一行中,我们把区域设置设为美式英语(1033),这(除其他影响外)会把小数分隔符设为小数点。否则,小数值会按区域设置显示,也就是可能用小数逗号而不是小数点,这在把这类值传给 IM 时会造成问题。字符串常量定义之后的两行是标准的开销,因为我们总是需要一个 Shell 对象,以便通过它的 Exec 或 Run 方法启动 IM 的程序。唯一的脚本参数是一个文件名,通常通过拖放或 SendTo 提供。在命名输出文件时,我们像 PTlens 那样,在原始文件名后追加 "pt",例如 _Photo.JPG -> Photo_pt.JPG。文件名存放在 strFileIn 中,我们由此推导出输出文件名 strFileOut。接着我们运行 IM 的 Identify 程序。结果(即表示焦距的 EXIF 有理数)存放在 strf 中。EXIF 有理数以分子/分母的形式给出,例如 82 / 10 = 8.2 mm。因此,在把该有理数用于计算参数 b 的公式之前,必须先对其求值。在最后两行中,我们构建 Convert 的命令行,并通过 Shell 对象的 Run 方法执行该语句。参数 7 会最小化窗口,TRUE 告诉脚本等待结果返回。上面的脚本勾勒了在 VBScript 中使用 IM 命令行工具(而非 COM+ 对象)时的一般策略。这些工具通过以下两种方式之一调用:
- 如果不需要文本输出,则通过 Shell 对象的 Run 命令
- 如果需要对文本输出求值(Identify 通常就是这种情况),则通过 Shell 对象的 Exec 命令。
这第一个 VBScript 所做的事情,其实用下面这样的批处理文件也能做到
SETLOCAL EnableDelayedExpansion
FOR /F %%i in ('identify -format "%%[EXIF:FocalLength]" %1') DO SET FL=%%i
FOR /F %%i IN ('Convert null: -format "%%[fx:0.000005142*(%FL%)^3 - 0.000380839 * (%FL%)^2 + 0.009606325 * %FL% - 0.075316854]" info:') DO SET B=%%i
Convert %1 -distort barrel "0.0 !B! 0.0" "%~dpn1_pt%~x1"
但 VBS 代码更直接明了,更容易修改,并且在错误检查等方面也更容易扩展。请注意,第二条 FOR 语句中不能换行,因此我们只能让它保持原样的长度。
处理多个文件
与 DOS 批处理文件相比,VBScript 的一个真正优势是可以轻松遍历任意数量的命令行参数。例如,你可以在 Windows 资源管理器中选中若干文件,然后通过 IM 的 Montage 把选中的图像合并成一张索引图。基本代码如下:
Dim wsh
Set wsh = CreateObject("Wscript.Shell")
'
strInputFiles = ""
For EACH Arg IN WScript.Arguments
strInputFiles = strInputFiles & " """ & Arg & """"
next
'
IndexFile= Left(WScript.Arguments(1),InStrRev(WScript.Arguments(1),"\")) & "index.jpg"
Command = "montage -geometry 210x140+0+5 -tile 6x " & strInputfiles & " -quality 80% """ & IndexFile & """"
wsh.run command, 7, true
脚本首先把通过拖放传给脚本的文件名连接起来。然后通过一些简单的字符串操作,在同一文件夹中推导出一个 JPEG 输出文件的名称。最后,用适当的参数调用 Montage。对于较大的脚本,把文件名保存在数组中会更方便:
Dim FName()
Dim wsh
Set wsh = CreateObject("Wscript.Shell")
'
NArgs = WScript.Arguments.Count
Redim FName(NArgs-1)
FOR i = 0 TO NArgs - 1
FName(i) = """" & WScript.Arguments(i) & """"
NEXT
我们先通过 Dim FName() 定义一个动态数组,然后通过 Redim FName(NArgs-1) 对其重新定维。Montage 的命令行可能会变得非常长,因为在输入文件列表中,每个文件都以其完全限定的文件名来指定。最大长度并非通常的 8192 个字符,而是由调用 ShellExecute 函数所允许的最大长度决定,在 Windows XP 上这只有 2048 个字符。当目录名非常长时,这会造成问题。所产生的错误无法由脚本处理,因为该错误发生在脚本执行之前。一个可行的解决办法是把文件放在名称较短的本地文件夹中。基于脚本的(部分)解决方案与批处理文件相同:如果只给出一个文件名,则处理父目录中的所有图像。为了处理某个文件夹内的所有 GIF,我们可以这样做:
Dim fs, folder
Set fs = CreateObject("Scripting.FileSystemObject")
If WScript.Arguments.Count <> 1 Then WScript.Quit(1)
set Folder = fs.getFolder(fs.GetParentFoldername(WScript.Arguments(0)))
FN=""
FOR EACH file in folder.files
If instr(file,"gif") <> 0 THEN FN = FN & File & vbLF
NEXT
MsgBox FN
这里我们使用 FileSystem 对象来确定父文件夹的名称。不过文件扩展名的匹配是按常规方式检查的,因为 Files 对象只提供 Type 属性作为替代。由于拖放或“发送到”会以随机顺序把文件名传给脚本,因此常常需要按字母顺序对文件名排序。这可以通过冒泡排序实现:
for i = 0 to NArgs - 1
for j = i + 1 to NArgs - 1
if FName(i) > FName(j) then
Temp = FName(i)
FName(i) = FName(j)
FName(j) = Temp
end if
next
next
右图展示了上述概念的一个更精巧的应用:一组视频帧通过 VBScript 与 ImageMagick 被组装成两条平行的“胶片带”。齿孔在视觉上提示了帧的推进方向是从上到下、即按列排列,而不是通常西方阅读习惯的从左到右再从上到下。执行这项工作的完整脚本可以从压缩包 strip.zip 下载。补充说明:每帧右上角的红色时间码是由一个 AVIsynth 脚本生成的。这些帧是从 VirtualDub 导出后转储的。由于嵌入了时间码,这些帧不一定需要在时间上等距,也就是说可以在 Windows 资源管理器中按需选择,并发送给放置在“发送到”文件夹中的那个脚本。
处理文本文件
在客户端计算机上使用脚本时,输入信息通常通过拖放或“发送到”提供,也就是说基本上由文件名组成,这些文件会按脚本预先定义的方式处理。任何额外的信息要么必须由用户在运行时交互提供,要么必须以文本文件的形式提供。基本上,我们有以下几种选择:
- 脚本接受图像文件作为输入,并附带一个(可能是可选的)提供额外信息的文本文件。
- 脚本接受单个文本文件作为输入,其中列出了要处理的图像以及所需的任何额外信息。
在前一种情况下,适合把这个可选的文本文件放在与图像相同的目录中,并为其指定一个标准名称。脚本随后可以从输入图像推导出父目录名称,如果存在的话,就在同一目录中打开该文本文件。前面提到的“胶片带”就是这种方式的一个例子:在脚本开头,我们大概会根据传给脚本的图像数量,定义某种标准的帧排列顺序。但也可能存在需要偏离这种标准帧顺序的情形。因此我们可以在帧所在目录中放一个名为 ordering.txt 的文本文件,如果它存在,就用来控制帧的顺序:
strTxtFile="ordering.txt"
PDir = fs.getParentFolderName(FName(0)) & "\"
Wsh.CurrentDirectory = PDir
If fs.FileExists(strTxtFile) then
Set objFile = fs.OpenTextFile(strTxtFile, 1)
bCtrlFile = True
NCols = objFile.ReadLine
objFile.close
else
bCtrlFile = False
end if
第二种概念的一个有用应用可以在 Fred Weinhaus 的网站上展示的、把图像透视映射到目标平面的做法中找到。我们可以利用这一概念,把一张有透视畸变的图像“贴皮”到一张透视(大体上)正确的版本上,如右图所示:在上半部分,左边的图像是在一起轻微事故现场拍摄的照片。右边的照片是在稍后一次探访现场时,从稍高的位置拍摄的。在下半部分,事故照片(即平坦的路面)通过透视变换被映射到目标照片上。(这样做的目的是可视化黑色汽车右前轮胎留下的淡淡刹车痕的方向角。)为了完成这项工作,用户必须在源图像中选取四个点,并在透视正确的图像中选取它们对应的目标点。我们可以手动完成这项工作,即在图像查看器(如 IrfanView)中通过取点来确定源图像和目标图像中的坐标,记下这些坐标,再将其提供给一条 Convert 命令行。不过,这项繁琐的工作可以借助免费程序 WinMorph 来简化,它提供了一个便捷的界面正好可以做这件事:从两张图片中选取一些源点及其对应的目标点。两张照片中的黄色折线连接了每张图片中所选的四个点。然而,变形(morphing)算法本身并不适合用来做透视校正。(该算法的基本原理在 IM 网站 Usage 部分的 Distorts 一节中有说明。关于其在变形方面可用性的演示,可以在 Fred Weinhaus 的 ShapeMorph 脚本中找到。)WinMorph 会把它的信息保存在一个结构化的文本文件中,其中(除其他信息外)包含源和目标的文件名,以及源点和目标点的坐标。因此,我们可以从 WinMorph 文件中推导出 IM 透视畸变所需的全部信息。你可以从文件 wmpr.zip 下载所涉及的全部文件和图像的副本。
管道处理
到目前为止,我们一直是通过 Shell 对象的 Run 或 Exec 函数直接调用 IM 的命令行工具(如 Convert、Identify、Montage)。但是,如果我们想使用 IM 的管道能力,即把前一个命令的输出送给下一个命令,就必须通过命令环境来调用命令行工具。例如,如果我们想裁掉上面脚本生成的索引图的白边,代码就要写成这样:
Dim wsh
Set wsh = CreateObject("Wscript.Shell")
'
strInputFiles = ""
For EACH Arg IN WScript.Arguments
strInputFiles = strInputFiles & " """ & Arg & """"
next
'
IndexFile= Left(WScript.Arguments(1),InStrRev(WScript.Arguments(1),"\")) & "index.jpg"
Command = "cmd /c montage -geometry 210x140+0+5 -tile 6x " & strInputfiles & " miff:- | magick - -trim """ & IndexFile & """"
wsh.run command, 7, true
这里我们用选项 /c 调用命令处理器 cmd,以便在完成后自动关闭命令行窗口。出于调试目的,我们也可以用 /k 选项调用它,这会让命令行窗口保持打开,方便我们查看可能出现的错误信息。
测试与调试 VBScript
基本上,我们是在用 VBScript 构建 IM 命令行工具的参数列表,然后要么直接运行这些工具,要么在 DOS 窗口中运行它们。这意味着首先应当确保:
- 命令行本身能按预期完成工作
- 命令行被脚本正确地构建出来。
所以一开始,应该先在 DOS 窗口中测试命令行本身。首次测试脚本时,不应直接运行 IM 命令,而应通过 MsgBox(strCommand) 在消息框中显示该文本字符串,因为如果命令行本身就是错的,任何调试工具都帮不上什么忙。这个简单的消息框对调试脚本也很有帮助,我从未真正觉得需要一个更复杂的调试器。至于运行时测试,应当确保:
- 能够正确访问 IM 的命令行工具
- 用户选择了你所期望她/他选择的内容,即若干文件(可能是特定类型)、一个目录、一个文本文件等等。
错误信息可以很容易地通过 MsgBox(...) 来显示。
更多信息
遗憾的是,目前尚无已知的(除本文外)专门介绍在 DOS 批处理文件中使用 ImageMagick 命令的教程。网站 DOS "For" Command Help 网页对 "FOR" 命令的用法有更好的说明。你可能也想看看 Bonzo 的 Batch Script 页面。