详解ImageMagick中SVG的支持

事实上ImageMagick是可以很好地支持svg的——当然,它只是将svg转成图片格式而不能反过来,毕竟它只是个图片处理工具。不论是在使用convert,identify还是mogrify等等时,只要操作的对象是一个.svg文件,那么ImageMagick就会启动这一机制。例如,当你使用identify查看一个.svg信息的时候,事实上你是在查看它转换后得到的一张.png图片:

> identify -verbose your.svg
Image: /var/folders/w9/fy0l4xgj3w73j4d71748v9nw0000gn/T/magick-481524PstlAlieWiB
  Base filename: your.svg
  Format: PNG (Portable Network Graphics)
  Mime type: image/png
  Class: DirectClass
...

ImageMagick主要有三种支持SVG的方式,包括内置(Internal)、内建(Build-in)和委托(Delegate)。

在高版本的ImageMagick中MSVG总是内置的;而稍早些的版本中,ImageMagick是混合了『内建』和『委托』两种方式来提供SVG的支持的。需要强调的是

请尽量不要直接使用内置的MSVG。

内置的MSVG

ImageMagick的高版本内置了一个SVG渲染器。它有两种称谓,在代码中叫做IMAGEMAGICK_SVG,例如在编译中提供相应的开关参数;而在用户级别的配置或接口中,它称为MSVG(这里的M是Magick的首字母)。它完整的名称是ImageMagick's own SVG internal renderer,所以自有SVG(Own SVG)内部渲染器(internal renderer)以及MSVG其实指的都是同一个东西:

> convert -list format | grep SVG
     MSVG  SVG       rw+   ImageMagick's own SVG internal renderer
      SVG  SVG       rw+   Scalable Vector Graphics (XML 2.9.4)
     SVGZ  SVG       rw+   Compressed Scalable Vector Graphics (XML 2.9.4)

注意这里的『XML 2.9.4』其实也是MSVG引擎的一部分,它实际上指的是libxml2这个库的版本,因为MSVG是基于xml解析并自行渲染的svg引擎。

由于MSVG存在很多限制,所以ImageMagick『千万百计』地隐藏了它的存在——哈哈,其实没有这么夸张,它们是承认这个东西的存在并且提供官方支持的(在这里)。

在ImageMagick 6.6.7-10之后的版本中,MSVG总是缺省内置支持(WITH_IMAGEMAGICK_SVG参数缺省打开),即使是在WITHOUT_X11状态下,编译过程也只是给出WARN级别的警告。

而在此前( 但晚于v6.3.3-5之后的)版本中,如果WITHOUT_X11,那么编译时将不会内置支持SVG。

参考:https://www.freshports.org/graphics/ImageMagick/

任何情况下,如果你只是想启用MSVG(以避开其它引擎的影响),那么你可以在input文件名上加上"msvg:"前缀(v6.3.4以后)。例如:

> identify -verbose 'msvg:your.svg'
Image: your.svg
  Base filename: b2t_8-2.svg
  Format: MVG (Magick Vector Graphics)
  Class: DirectClass
  Geometry: 485x306+0+0
...

你也可以使用-draw参数来开启一个MSVG的画板并操作它。例如(将这块画板作为源,转换成PNG格式输出):

# http://www.imagemagick.org/Usage/draw/#svg
> convert -size 10x6 xc:skyblue  -fill black \
          -draw 'color 6,3 point' -scale 100x60 draw_color_point.png

内建模式及其开启

如果ImageMagick在安装时包含librsvg模块,那么就会启用内建模式(所以Build-in SVG也往往直接称为RSVG)。一旦包含这个模块,那么它的优先级比内置的MSVG要高。

如果你是使用brew来安装的ImageMagick,那么你可以用:

> brew info imagemagick
...
Optional: fontconfig ✔, little-cms ✘, little-cms2 ✔, libwmf ✘, librsvg ✔ ...

来查看当前环境是否能支持rsvg。如上,librsvg已经安装了,那么就可以通过安装参数来开启它(或使用brew reinstall重新安装):

# 你可能需要先安装librsvg模块
#	- brew install librsvg
> brew install imagemagick --with-librsvg

如果你不是使用brew,那么你可以使用如下命令来查看:

> convert -list configure | grep -Eoe '--with-rsvg(=[^\-]+)?'
--with-rsvg=no

如果没有rsvg的支持,那么你需要自己编译ImageMagick,相关的操作在这里

如果你成功安装了rsvg支持,那么可以看到如下信息:

# build-in的delegate中包含了rsvg
> identify -version | grep --color rsvg
Delegates (built-in): bzlib cairo fontconfig freetype jng jpeg ltdl lzma png rsvg tiff xml zlib

# 或format中使用了rsvg来支持svg
> identify -list format | grep -i svg
convert -list format | grep -i svg
     MSVG  SVG       rw+   ImageMagick's own SVG internal renderer
      SVG  SVG       rw+   Scalable Vector Graphics (RSVG 2.40.17)
     SVGZ  SVG       rw+   Compressed Scalable Vector Graphics (RSVG 2.40.17)

注1:一些资料中称需要使用--use-rsvg参数在编译中开启rsvg支持,这可能是一个被放弃的、早期的构建参数。

注2:在brew中用的参数是--with-librsvg,而实际上编译时使用的是--with-rsvg参数。这个转换操作是在brew安装用的formula文件中完成的。

委托模式的开启

通常情况下,ImageMagick会在内置的MSVG和内建模式(RSVG)之间选择一个;如果二者都没有,那么它会选择第三种,也就是委托模式。

你总是可以通过如下命令查看ImageMagick委托(外部程序)来进行的图形转换,包括SVG:

> convert -list delegate | grep 'svg'
...
svg =>          "rsvg-convert' -o '%o' '%i"

注:严格来说,前面说的内建模式事实上也是一种委托。只不过它是在编译时build-in的,所以在--help参数显示的信息中就能直接看到,并且会影响-format显示的格式支持;而在-list delegate 所列举的委托关系中,却并不会出现。

由于只有MSVG和rsvg都不生效时才会开启委托模式,但高版本中ImageMagick中总是内置MSVG,所以是很难触发委托模式的。因此,如果你做这个尝试,那么你需要一个不内置MSVG的低版本ImageMagick,并且在编译中关闭--with-rsvg选项。OK,这样你就可以看到ImageMagick调用了上述委托中的rsvg-convert来进行转换。

当然,也有例外,因为其实还是有一个后门来做这件事的,这就是所谓的内部委托。

内部委托

准确地说法:MSVG这个内部渲染器提供了一个『内部委托(special internal delegates)』。这种内部委托与上面的rsvg-convert采用的是类似机制,但是通过-list delegate命令是无法查看到的——你得打开配置文件自己来找:

# 查看你的ImageMagick将读取哪些配置文件
> convert -debug all -list delegate 2>&1 | grep 'delegates.xml'
Searching for configure file: ...
...
Path: /usr/local/Cellar/imagemagick/7.0.5-9/etc/ImageMagick-7/delegates.xml

注意前面有一个搜索优先顺序的列表(你可能用得上),不过这里我们只需要关心最后这个Path就好了。打开这个文件,查找svg:decode,将stealth="True"删除掉。现在你就可以看到它了:

> convert -list delegate | grep -Ee 'svg(:decode)? ='
        svg =>          "rsvg-convert' -o '%o' '%i"
 svg:decode =>          "inkscape' '%s' --export-png='%s' --export-dpi='%s' --export-background='%s' --export-background-opacity='%s' > '%s' 2>&1"

注:内部委托与一般的(外部程序的)委托模式处理逻辑是一样的,只是在高版本的ImageMagick用svg:decode替代了svg这个入口而已。

是的,你应该已经注意到了,这个内部的委托指向了inkscape。你可以安装inkscape来支持这个委托。例如:

> brew cask install xquartz inkscape

接管内部委托

你也可以自己写个脚本来将操作转发到你的处理程序(类似于代理)。例如:

#!/bin/bash
DPI="${3##*=}"
rsvg-convert --format=png --output="${2##*=}" --dpi-x="${DPI%%,*}" --dpi-y="${DPI##*,}" --background-color="${4##*=}" "$1"

将这个脚本另存到搜索路径中,并命名为inkscape,加上可执行权限。OK,你就看到svg:decode的相关调用转发到你的脚本中,并交给rsvg-convert处理了。

注意:如果内部委托调用出错(程序退出代码大于0),那么ImageMagick将再次调用MSVG来完成处理。

当然,我们也可以把这个接管的脚本程序写得通用一点。比如将delegates.xml中的svg:decode项修改到一个统一的模式(以后就可以不用改了,不过sv要注意其中的YOUR_SCRIPT_NAME应该修改得与后面的脚本名一致):

<delegate decode="svg:decode" command="&quot;YOUR_SCRIPT_NAME&quot; &quot;%s&quot; &quot;%s&quot; &quot;%s&quot; &quot;%s&quot; &quot;%s&quot; &gt; &quot;%s&quot; 2&gt;&amp;1"/>

然后安装自己的工具(下面以cairosvg这个工具为例,首先安装它):

# @see: http://cairosvg.org/
> pip3 install cairosvg

然后写调用脚本(YOUR_SCRIPT_NAME):

#!/bin/bash

# in arguments:
#	inFile outFile dpi bgColor bgOpacity
DPIX="${3%%,*}"  # format - xRes,yRes
BGCOLOR="$4" # format - string, #xxxxxx, or rgb(r%,g%,b%)
cairosvg -f png -o "$2" -d "$DPIX" "$1"

最后将这个脚本另存、更名、更改可执行权限。

在你使用identify、convert等等工具处理svg时,都将调用上述脚本。所以,现在你可以随意用新的转换工具来替换MSVG了。

我的开源项目svg-provider是一个较完整的实现,其主要是在参数的处理上面进行了优化,并且可以适配更多的转换工具。