ImageMagick 示例 -- 绘图
在 IM 中,绘图就是向已有图像添加新元素的方式。虽然很多文字绘制内容已经在 复合字体效果 和 图像标注 的示例页中介绍过,本页讨论的是 "[-draw](https://imagemagick.org/command-line-options/#draw)" 运算符更通用的其他方面。draw 命令最初是用来创建简单图像的,但随着时间推移,它已经扩展为矢量图形到栅格图像转换的接口。
ImageMagick Draw 命令 计算机中的图像通常以两种不同方式保存。第一种,也是你在这些示例页面中最常见到的方式,称为栅格图形。在这种方式中,图像以矩形像素数组的形式存储。另一种方式不那么常见,也不那么容易修改,但从另一个角度看更灵活,这就是对象矢量图形。在这种形式中,图像用线条、弧线、颜色填充,有时还有深度来描述。这很有用,因为你可以把这些图像缩放到几乎任何想要的尺寸,它们仍然能完美显示。与等价的栅格格式相比,你还可以用很少的空间描述非常大而复杂的图像。矢量图形图像的例子包括 PostScript,以及新的 SVG -- 可缩放矢量图形。True-Type 字体也是矢量图形的例子,因为这允许单个字符描述在任意尺度下使用。"[-draw](https://imagemagick.org/command-line-options/#draw)" 图像运算符,是通向 ImageMagick 矢量绘图函数的窗口,并形成了一组与普通 IM 命令行图像运算符相当独立的命令。 | _通用的矢量图形文件格式只有少数几种,因为每种这类格式通常都与其他格式很不相同。结果就是几乎没有多少代码可以共享。
因此,ImageMagick 更关注使用矢量图形来绘制 SVG 格式图像。PostScript 和 true-type 字体图形会交给其他外部的 'delegate' 库和应用程序处理,因为它们更适合绘制这些类型的矢量图形格式。
这并不是说 SVG 没有可用的 delegate。一个例子是在编译时可用的 RSVG 库或 GTK SVG 库。IM 会链接这些库来 magick SVG,而不是尝试自己完成。_
---|---
绘图基元命令
让我们从 "[-draw](https://imagemagick.org/command-line-options/#draw)" 图像运算符的 MVG 命令中最古老、最简单、也最常见的绘图基元开始。注意,所有参数都会按浮点数处理,不必是整数;这些示例中我通常使用整数只是习惯。 |
# Single Pixel Draw (two ways -- these have been enlarged)
# Point 'paints' the color pixel
magick -size 10x6 xc:skyblue -fill black \
-draw 'point 3,2' -scale 100x60 draw_point.gif
# Color Point 'replaces' the color pixel
magick -size 10x6 xc:skyblue -fill black \
-draw 'color 6,3 point' -scale 100x60 draw_color_point.gif
![[IM 输出]](../static/img/draw/draw_point.gif)
![[IM 输出]](../static/img/draw/draw_color_point.gif)
在涉及半透明颜色时,这两种点方法会按照注释所说产生不同结果。详情请参见下面的 颜色填充基元。 |
# Rectangle / Rounded Rectangle / Rectangular Arc
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "rectangle 20,10 80,50" draw_rect.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "roundrectangle 20,10 80,50 20,15" draw_rrect.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "arc 20,10 80,50 0,360" draw_arc.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "arc 20,10 80,50 45,270" draw_arc_partial.gif
![[IM 输出]](../static/img/draw/draw_rect.gif)
![[IM 输出]](../static/img/draw/draw_rrect.gif)
![[IM 输出]](../static/img/draw/draw_arc.gif)
![[IM 输出]](../static/img/draw/draw_arc_partial.gif)
'arc' 绘图基元和矩形列在一起,因为它实际上只是一个适配到由两个坐标定义的 'rectangle' 内部的 'ellipse'。局部弧线很少使用,因为除非角度限制为九十度的倍数,否则很难确定端点。
'circle' 和 'ellipse' 基元涉及 'center' 坐标,并分别配合一个 'edge' 坐标,或 'size' 与 'angle' 值。 |
# Circle / Ellipse (centered on a point)
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "circle 50,30 40,10" draw_circle.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "ellipse 50,30 40,20 0,360" draw_ellipse.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "ellipse 50,30 40,20 45,270" draw_ellipse_partial.gif
![[IM 输出]](../static/img/draw/draw_circle.gif)
![[IM 输出]](../static/img/draw/draw_ellipse.gif)
![[IM 输出]](../static/img/draw/draw_ellipse_partial.gif)
你也可以查看 Push/Pop Context,其中有一个创建旋转椭圆的示例。 |
# Line / Polyline / Polygon / Bezier
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "line 20,50 90,10" draw_line.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "polyline 40,10 20,50 90,10 70,40" draw_polyline.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "polygon 40,10 20,50 90,10 70,40" draw_polygon.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "bezier 40,10 20,50 90,10 70,40" draw_bezier.gif
![[IM 输出]](../static/img/draw/draw_line.gif)
![[IM 输出]](../static/img/draw/draw_polyline.gif)
![[IM 输出]](../static/img/draw/draw_polygon.gif)
![[IM 输出]](../static/img/draw/draw_bezier.gif)
绘制线条和曲线的更好方法是使用 SVG 路径绘图,它灵活得多,甚至允许进行“相对线条绘制”。 |
# text drawing / image
magick -size 100x60 xc:skyblue -fill white -stroke black \
-font Candice -pointsize 40 -gravity center \
-draw "text 0,0 'Hello'" draw_text.gif
magick -size 100x60 xc:skyblue -gravity center \
-draw "image over 0,0 0,0 'terminal.gif'" draw_image.gif
![[IM 输出]](../static/img/draw/draw_text.gif)
![[IM 输出]](../static/img/draw/draw_image.gif)
最后这两种填充类操作,是目前唯一会受到 "[-gravity](https://imagemagick.org/command-line-options/#gravity)" 影响的绘图操作。这些操作的其他修饰项包括: "[-fill](https://imagemagick.org/command-line-options/#fill)"、"[-tile](https://imagemagick.org/command-line-options/#tile)"、"[-origin](https://imagemagick.org/command-line-options/#origin)"、"[-stroke](https://imagemagick.org/command-line-options/#stroke)"、"[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)"、"[-font](https://imagemagick.org/command-line-options/#font)"、"[-pointsize](https://imagemagick.org/command-line-options/#pointsize)"、"[-box](https://imagemagick.org/command-line-options/#box)"。还有其他修饰项,但它们与更高级的 Magick 矢量图形语言 相关。
Bezier 基元
'bezier' 基元用于绘制曲线。每条命令只会绘制一个曲线段。通常会给出 4 个点(8 个数字):一个起点 'knot'、两个控制点和一个终点 'knot'。两个控制点定义曲线偏离所连接端点 'knot' 的方向和速度。要平滑连接两段曲线,应当让末端的控制点通过 'knot' 镜像,形成下一条 Bezier 曲线中的控制点。例如,这里我绘制了两条平滑连接在一起的 bezier 曲线。注意控制线和控制点(也一并绘制了)如何在连接坐标处沿角度和长度都直接镜像。这个细节很重要,否则曲线不会平滑。 |
points="10,10 30,90 25,10 50,50 50,50 75,90 70,10 90,40"
clines=`echo "$points" | sed 's/ /\n/g' |\
while read line; do echo "line $line"; done`
symbols=`echo path "'"; for point in $points; do
echo "M $point l -2,-2 +4,+4 -2,-2 l -2,+2 +4,-4 -2,+2"
done; echo "'"`
magick -size 100x100 xc:skyblue -fill none \
-draw "stroke gray $clines stroke blue $symbols " \
-draw "stroke red bezier 10,10 30,90 25,10 50,50 " \
-draw "stroke red bezier 50,50 75,90 70,10 90,40 " \
draw_bezier_joined.gif
![[IM 输出]](../static/img/draw/draw_bezier_joined.gif)
如果我移动其中一个控制点,使它没有通过所连接的 'knot' 从同一 'knot' 的另一个控制点“反射”过去,那么曲线就会不连续。 |
points="10,10 30,90 25,10 50,50 50,50 80,50 70,10 90,40"
clines=`echo "$points" | sed 's/ /\n/g' |\
while read line; do echo "line $line"; done`
symbols=`echo path "'"; for point in $points; do
echo "M $point l -2,-2 +4,+4 -2,-2 l -2,+2 +4,-4 -2,+2"
done; echo "'"`
magick -size 100x100 xc:skyblue -fill none \
-draw "stroke gray $clines stroke blue $symbols " \
-draw "stroke red bezier 10,10 30,90 25,10 50,50 " \
-draw "stroke red bezier 50,50 80,50 70,10 90,40 " \
draw_bezier_disjoint.gif
![[IM 输出]](../static/img/draw/draw_bezier_disjoint.gif)
如果再次移动控制点,让它与相关的 'knot' 点重合,那么线条会直接从该点伸出,完全没有任何“曲线”。 |
points="10,10 30,90 25,10 50,50 50,50 50,50 70,10 90,40"
clines=`echo "$points" | sed 's/ /\n/g' |\
while read line; do echo "line $line"; done`
symbols=`echo path "'"; for point in $points; do
echo "M $point l -2,-2 +4,+4 -2,-2 l -2,+2 +4,-4 -2,+2"
done; echo "'"`
magick -size 100x100 xc:skyblue -fill none \
-draw "stroke gray $clines stroke blue $symbols " \
-draw "stroke red bezier 10,10 30,90 25,10 50,50 " \
-draw "stroke red bezier 50,50 50,50 70,10 90,40 " \
draw_bezier_no_curve.gif
![[IM 输出]](../static/img/draw/draw_bezier_no_curve.gif)
如果两个控制点都设置为各自的 'knot',就会生成一条直线。 |
points="10,10 10,10 50,50 50,50 50,50 50,50 90,40 90,40"
clines=`echo "$points" | sed 's/ /\n/g' |\
while read line; do echo "line $line"; done`
symbols=`echo path "'"; for point in $points; do
echo "M $point l -2,-2 +4,+4 -2,-2 l -2,+2 +4,-4 -2,+2"
done; echo "'"`
magick -size 100x100 xc:skyblue -fill none \
-draw "stroke gray $clines stroke blue $symbols " \
-draw "stroke red bezier 10,10 10,10 50,50 50,50 " \
-draw "stroke red bezier 50,50 50,50 90,40 90,40 " \
draw_bezier_lines.gif
![[IM 输出]](../static/img/draw/draw_bezier_lines.gif)
如果不指定全部 4 个点,'bezier' 基元实际上并不太有用。只有第一个点和最后一个点被归类为曲线会经过(或结束于)的 'knot'。其余中间点都纯粹视为控制点,会按照给出的顺序影响曲线;控制点离得越远,对该曲线段的影响就越大。 |
points="10,10 30,90 25,10 75,90 70,10 90,40"
symbols=`for point in $points; do
echo "M $point l -2,-2 +4,+4 -2,-2 l -2,+2 +4,-4 -2,+2"
done`
magick -size 100x100 xc:skyblue -fill none \
-draw "stroke gray polyline $points " \
-draw "stroke red bezier $points " \
-draw "stroke blue path '$symbols' " \
draw_bezier_multi.gif
![[IM 输出]](../static/img/draw/draw_bezier_multi.gif)
为保持简单,不建议每个 'bezier' 曲线段使用多于或少于 4 个点。实际上,我建议完全不要使用 'bezier' 基元,而是改用 SVG 路径三次 Bezier 来生成曲线。它有一个特殊的 'S" 曲线延续函数,会自动执行合适的控制点“反射”,从而生成平滑连接的曲线段,并减少你需要使用的控制点数量。你还可以定义相对于路径上最后一个端点的点。
颜色填充基元
在上述“简单”基元之上,"[-draw](https://imagemagick.org/command-line-options/#draw)" 还提供了一组颜色填充或修改基元。它们会根据所选方法,从指定点开始修改图像中的颜色。这些填充方法实际上并不是真正的“draw”命令,而是颜色替换函数。它们被添加到 draw 中,是因为在程序很早期的版本中,这是把这些操作插入 ImageMagick 的最容易位置。与上面一样,所用颜色由 "[-fill](https://imagemagick.org/command-line-options/#fill)" 颜色设置指定;但如果设置了 "[-tile](https://imagemagick.org/command-line-options/#tile)",则会改用该图像。上面的其他设置选项不会被使用,对这些操作也没有影响。还有两个额外设置也会作用于这些基元,即 "[-bordercolor](https://imagemagick.org/command-line-options/#bordercolor)" 和 "[-fuzz](https://imagemagick.org/command-line-options/#fuzz)" 因子设置。不过这些设置不能在 'MVG' 语言内部定义,所以只能在使用 "[-draw](https://imagemagick.org/command-line-options/#draw)" 运算符之前设置。第一个 'color point' 你已经在上面的示例中见过,它是 'point' 绘图基元的替代方式。如果仔细看,你会看到我们在测试图像中设置的那个白色像素。
|
magick color_test.png -fill white \
-draw 'color 30,20 point' color_point.png
![[IM 输出]](../static/img/draw/color_point.png)
不过,在绘制透明和半透明颜色时,这些函数并不相同。这里有一个三像素的红色图像(已放大),第二个也就是中间的像素,我们使用 'point' 函数用半透明蓝色在红色像素上绘制,得到紫色结果。但如果使用 'color point' 函数(最后一个,也就是右侧像素),红色会被半透明蓝色像素完全替换。它不是叠加上去的。 |
magick -size 3x1 xc:red -alpha on -fill '#00F8' \
-draw 'point 1,0' \
-draw 'color 2,0 point' -scale 33x33 draw_points.png
![[IM 输出]](../static/img/draw/draw_points.png)
所有 'color' 函数都会进行完整的颜色替换,而所有其他颜色基元都是把颜色“绘制”到图像上方。因此,你可以使用 'color' 来绘制透明颜色。'color replace' 绘图函数会替换指定位置处与给定精确颜色相同的所有实例。并且如你所见,这些区域不必彼此连通。 |
magick color_test.png -fill white \
-draw 'color 30,20 replace' color_replace.png
magick color_test.png -fill white -fuzz 13%\
-draw 'color 30,20 replace' color_replace_fuzz.png
![[IM 输出]](../static/img/draw/color_replace_fuzz.png)
不过,如第一个结果所示,边缘上的一些像素没有被替换。这些像素与所选像素的颜色并非_完全_相同,所以被忽略了。添加一个较小的 fuzz 因子,也会把与原始颜色相近的颜色包含进来,如上面的第二个示例所示。当然,“fuzz 因子”并不是完美方案,因为它无法捕获所有这类边缘像素。这是所有这些“颜色填充”方法都会反复遇到的问题,而且没有通用解法。如果你想替换某个已知的特定颜色,而不是从图像本身选择一种颜色,那么可以改用 "[-opaque](https://imagemagick.org/command-line-options/#opaque)" 图像运算符。该函数也使用 "[-fuzz](https://imagemagick.org/command-line-options/#fuzz)" 因子设置来扩大与给定颜色匹配的颜色范围。'floodfill' 方法也相当简单,因为它只会填充所选点周围的整个区域,不会选择任何虽然颜色相近但没有以某种方式连通的其他区域。你也可以使用 "[-fuzz](https://imagemagick.org/command-line-options/#fuzz)" 把相近颜色包含进来,从而扩大填充区域。在本例中,我们选择了足够高的值,使十字边界也被包含进来,从而让漫水填充“漏”到图像另一侧。 |
magick color_test.png -fill white \
-draw 'color 30,20 floodfill' color_floodfill.png
magick color_test.png -fill white -fuzz 15% \
-draw 'color 30,20 floodfill' color_floodfill_fuzz.png
![[IM 输出]](../static/img/draw/color_floodfill_fuzz.png)
用一种颜色对区域进行漫水填充并非没有问题。颜色可能会穿过很薄的边界,漏到不希望它进入的区域(演示见 背景图案上的 GIF)。或者它可能无法一直填充到所选区域的边缘(见 抗锯齿与漫水填充的问题)。但它确实能工作。'filltoborder' 类似于 'floodfill',不同之处在于你指定的是包围待填充区域的颜色,而不是填充过程中要被替换的颜色。当然,也建议使用 fuzz 因子,把“相近颜色”也纳入边界颜色选择,从而进一步限制 floodfill。 |
magick color_test.png -fill white -bordercolor royalblue \
-draw 'color 30,20 filltoborder' color_filltoborder.png
magick color_test.png -fill white -bordercolor blue \
-draw 'color 30,20 filltoborder' color_filltoborder2.png
magick color_test.png -fill white -bordercolor blue -fuzz 30% \
-draw 'color 30,20 filltoborder' color_filltoborder_fuzz.png
![[IM 输出]](../static/img/draw/color_filltoborder.png)
![[IM 输出]](../static/img/draw/color_filltoborder2.png)
![[IM 输出]](../static/img/draw/color_filltoborder_fuzz.png)
最后一种 draw 颜色方法是 'reset',它只是把整幅图像替换或重置为填充颜色。在这种情况下,实际选中的像素对结果完全没有影响。 |
magick color_test.png -fill white \
-draw 'color 30,20 reset' color_reset.png
![[IM 输出]](../static/img/draw/color_reset.png)
这实际上非常有用,因为它提供了一种简单方法,可以从已有图像生成纯色(或平铺图像)画布。(有关这一点以及实现同样效果的其他方法,请参见 按已有图像尺寸创建画布)。FUTURE:使用 "[-tile](https://imagemagick.org/command-line-options/#tile)" 图案来填充区域。
Alpha 填充基元
'alpha' 绘图基元的工作方式与上面描述的 'color' 基元完全相同,只是它不会替换所选区域的颜色,只会替换所选区域的 'alpha' 通道。(也就是说,这些填充函数只会调整 'alpha' 通道)。与 'color' 填充函数一样,'alpha' 值使用填充颜色(除非以 "[-tile](https://imagemagick.org/command-line-options/#tile)" 作为要使用的 'alpha value' 来源)。这里我们使用上面同一个 'color floodfill' 示例,但只调整 alpha 通道,让被填充部分变为完全透明。也就是说,原来的颜色仍然存在,只是透明了! |
magick color_test.png -fill none \
-draw 'alpha 30,20 floodfill' matte_floodfill.png
magick color_test.png -fill none -fuzz 15% \
-draw 'alpha 30,20 floodfill' matte_floodfill_fuzz.png
![[IM 输出]](../static/img/draw/matte_floodfill_fuzz.png)
'alpha reset' 函数也可用于让整幅图像变为半透明。当然在这种情况下,我们必须输出为 PNG,因为 PNG 可以接受图像中的半透明颜色。 |
magick color_test.png -fill '#00000080' \
-draw 'alpha 30,20 reset' matte_reset.png
![[IM 输出]](../static/img/draw/matte_reset.png)
注意,操作中没有使用 'black' 颜色分量,只使用了颜色的 alpha 分量。图像原有颜色保持不变。FUTURE:使用 "[-tile](https://imagemagick.org/command-line-options/#tile)" 图案来产生有趣的 alpha 效果。'color' 和 'alpha' 都是完整替换颜色的函数,总会生成布尔(全有或全无)类型的颜色替换。因此,这类区域的边缘总会显示出 锯齿化效果。因此,除了设置 GIF 图像的透明区域(它们同样是布尔式的)之外,它们通常并不是适合一般图像开发的图像运算符。不过也不是全无办法,这可以在 背景移除 的示例中看到。
关于 Draw 命令的细节
像素坐标
"[-draw](https://imagemagick.org/command-line-options/#draw)" 命令(以及 IM 中的许多其他命令)使用所谓的“像素坐标”。也就是说,'10,10' 这个坐标是从左上角向右并向下数 10 个像素处那个像素的中心。在这个坐标系统中,0,0 是左上角像素的中心,w-1,h-1 是右下角像素的中心。实际边缘位于 -0.5,-0.5 和 w-0.5,h-0.5,而中心像素(如果图像尺寸为奇数)位于 '(w-1)/2,(h-1)/2'。不过,当你以数学方式处理图像时(例如使用 distort),实际像素并没有真正意义,因此会使用“图像坐标”。在这个系统中,图像的实际边缘位于 '0,0' 和 'w,h'。而图像中心(可能是,也可能不是某个像素的中心)位于 'w/2,h/2'。要把“像素坐标”转换为图像坐标,加上 ½。因此左上角像素的中心是 '0.5,0.5',右下角像素是 'w-0.5,h-0.5'。示例:小图像中圆的中心
使用伽马与色彩空间校正进行绘图
与几乎所有 ImageMagick 操作一样,"[-draw](https://imagemagick.org/command-line-options/#draw)" 是一个线性运算符。因此它在线性 RGB 色彩空间中工作。这意味着,为了得到漂亮平滑的边缘,你可能需要在保存图像之前对图像做一些伽马校正,使其以非线性(伽马校正后的)sRGB 色彩空间存储。例如,如果你绘制一个大圆,然后直接保存它…… |
magick -size 81x81 xc:black -fill white -draw 'circle 40,40 40,3' \
circle_raw.png
![[IM 输出]](../static/img/draw/circle_raw.png)
看看圆的边缘,它们实际上看起来并不真的很平滑。你可以看到明显的阶梯效果。原因是你在线性 RGB 色彩空间中绘制了圆,却随后把图像当作真正的 sRGB 色彩空间来保存!要修正这一点,我们需要在保存图像之前加入伽马校正。 |
magick -size 81x81 xc:black -fill white -draw 'circle 40,40 40,3' \
-gamma 2.2 circle_gamma.png
![[IM 输出]](../static/img/draw/circle_gamma.png)
现在圆的边缘确实像应该的那样平滑而圆润。如果想正确地做到这一点,我们其实应该使用色彩空间来进行校正。不过,由于 IM 假定 RGB 是保存时的默认色彩空间,你需要做一些有点绕的处理,才能让它正确工作。 |
magick -size 81x81 xc:black -set colorspace RGB \
-fill white -draw 'circle 40,40 40,3' \
-colorspace sRGB circle_sRGB.png
![[IM 输出]](../static/img/draw/circle_sRGB.png)
| 注意,sRGB 色彩空间(这是保存图像的正确方式)与简单应用 2.2 伽马校正并不完全相同。不过两者结果的差异很小,并且只会在非常非常暗的图像中可见。
---|---
| 在 IM v6.7.5-1 之前,色彩空间名称 'sRGB' 和 'RGB'(linear-RGB)实际上是反过来的。因此,在较旧版本的 IM 上,上面两个标签应当互换。
---|---
要在真实图像上正确绘制(或进行任何“线性”图像处理)(在 IMv7 中),需要先移除已有伽马,处理图像,然后再恢复该伽马校正。更多细节请参见 使用色彩空间校正调整尺寸。下面是在真实图像上绘图的示例……先是不做任何颜色校正(raw),然后分别使用伽马和色彩空间校正。 |
magick rose: -fill none -stroke white -draw 'line 5,40 65,5' rose_raw.png
magick rose: -gamma .454545 \
-fill none -stroke white -draw 'line 5,40 65,5' \
-gamma 2.2 rose_gamma.png
magick rose: -colorspace RGB \
-fill none -stroke white -draw 'line 5,40 65,5' \
-colorspace sRGB rose_sRGB.png
![[IM 输出]](../static/img/draw/rose_sRGB.png)
如你所见,使用伽马或色彩空间校正后,线条看起来非常平滑,没有直接绘制时能看到的那种参差不齐的“阶梯”锯齿效果。(你需要一台很好的显示器才能看出来) | 上面的线条是使用 "[-stroke](https://imagemagick.org/command-line-options/#stroke)" 颜色绘制的。你也可以使用 "[-fill](https://imagemagick.org/command-line-options/#fill)" 来绘制线条,并得到相同结果,但这样就无法使用 "[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)" 控制线条粗细。更多信息请参见下面的 描边颜色设置。
---|---
| _颜色名称实际上是用 'sRGB' 色彩空间的值定义的,但 draw 会把它们当作图像处于 linear-RGB 色彩空间来应用。因此,如果对具名颜色('white' 或 'black' 以外)使用上面的伽马校正,会导致这些颜色发生扭曲。在这种情况下,最好不要使用伽马或色彩空间校正,这样具名颜色才能正确映射。
把具名 'sRGB' 颜色正确映射到要绘制到的图像色彩空间,将作为 IMv7 开发的一部分得到修复。
Stroke、StrokeWidth 与 Fill 的交互
"[-stroke](https://imagemagick.org/command-line-options/#stroke)" 和 "[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)" 选项用于在字体边缘周围绘制轮廓。这些选项通常与 "[-fill](https://imagemagick.org/command-line-options/#fill)" 一起使用,只需很少的工作就能让文字更有趣。
magick -size 380x70 xc:lightblue -pointsize 50 -font Chisel \
-fill green -stroke black -draw 'text 10,55 "Black Border"' \
stroke_font.jpg
默认设置是 "[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth) 1" 和 "[-stroke](https://imagemagick.org/command-line-options/#stroke) None"。但这会让轮廓描边不可见,只留下 "[-fill](https://imagemagick.org/command-line-options/#fill)" 颜色,因此你看不到它。"[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)" 在 "[-stroke](https://imagemagick.org/command-line-options/#stroke)" 为“不可见”时唯一的影响是在字体尺寸属性上,这意味着它仍然可能影响字体定位,以及 Label 和 Caption 图像生成的尺寸。除此之外,在你让描边可见之前,宽度没有可见效果。为了展示 "[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)" 实际如何影响字体外观(当它可见时),这里我用从“关闭”到逐渐变大的不同宽度绘制了一些文字。
magick -size 320x420 xc:lightblue -pointsize 70 -font Vademecum \
-fill red -stroke none -draw 'text 30,80 "Stroke -"' \
-fill red -stroke black -strokewidth 0 -draw 'text 30,160 "Stroke 0"' \
-fill red -stroke black -strokewidth 1 -draw 'text 30,240 "Stroke 1"' \
-fill red -stroke black -strokewidth 2 -draw 'text 30,320 "Stroke 2"' \
-fill red -stroke black -strokewidth 3 -draw 'text 30,400 "Stroke 3"' \
stroke_table.jpg
从上面的例子可以看出,把“[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)”设为“0”,并不等同于把“[-stroke](https://imagemagick.org/command-line-options/#stroke)”颜色设为“none”(默认值)。前者会生成一圈非常非常细的描边轮廓,而后者实际上会把描边关掉。两种情况下描边仍然会被绘制。不过还要注意,即使“[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)”为“0”,图像轮廓仍会比单纯“填充”的图像(使用“[-stroke](https://imagemagick.org/command-line-options/#stroke)”颜色“none”)略微向外扩展一点点。基本上,任何小于“1.0”的宽度都不能正常工作。在这可能产生影响的场合应格外小心。还要记住,“[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)”也是一个浮点设置。也就是说,描边宽度“0.5”同样有效。不过这通常只有在尝试绘制细位图圆,并且关闭抗锯齿时才重要。
这里是一个使用极大描边宽度的例子。
magick -size 320x100 xc:lightblue -font Candice -pointsize 72 -fill white \
-stroke black -strokewidth 15 -draw "text 25,65 'Anthony'" \
stroke_thick.jpg
注意,“[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)”会同时向内和向外扩展线条。下面是同一个例子,但再次绘制字体时不带描边轮廓,用来去掉非常粗的描边内部部分。
magick -size 320x100 xc:lightblue -font Candice -pointsize 72 -fill white \
-stroke black -strokewidth 15 -draw "text 25,65 'Anthony'" \
-stroke none -draw "text 25,65 'Anthony'" \
stroke_outline.jpg
更多使用描边的例子请参见复合字体效果。尤其可以看看“气球效果”。
绘制(描边)线条
IM 默认的线条绘制有几个奇怪的行为,值得了解。这里是默认的线条绘制…… |
magick -size 100x40 xc:lightblue \
-draw "line 5,35 95,5" \
line_default.jpg
![[IM 输出]](../static/img/draw/line_default.jpg)
可以用“[-fill](https://imagemagick.org/command-line-options/#fill)”选项设置线条颜色。 |
magick -size 100x40 xc:lightblue \
-fill white -draw "line 5,35 95,5" \
line.jpg
![[IM 输出]](../static/img/draw/line.jpg)
也可以通过设置“[-stroke](https://imagemagick.org/command-line-options/#stroke)”颜色,让线条稍微变粗。 |
magick -size 100x40 xc:lightblue \
-fill white -stroke black -draw "line 5,35 95,5" \
line_stroke.jpg
![[IM 输出]](../static/img/draw/line_stroke.jpg)
那么,我们用“[-fill](https://imagemagick.org/command-line-options/#fill)”选项指定的白色到哪里去了?这正是 ImageMagick 绘制线条时比较绕的地方。程序实际上会把线条看作一个约 1 像素宽的填充对象。这很自然,因为通常会用多条线扫出一个需要填充的区域。所以就像上一节给字体使用描边一样,IM 会先用填充色绘制线条(或对象),再用描边色围绕它绘制。结果就是,上面那条带描边色的线现在稍微变粗,而填充色完全藏在下面。如果把描边色设为半透明,就能再次看到那层填充色。总结来说,线条看起来会用“[-fill](https://imagemagick.org/command-line-options/#fill)”颜色绘制;但一旦“[-stroke](https://imagemagick.org/command-line-options/#stroke)”颜色被定义为默认的“none”或“transparent”以外的颜色,该选项就不再起作用。 | 选项“-linewidth”实际上只是“[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)”的别名,不应使用。
---|---
例如,你可能会以为这个命令会生成一条非常粗的线。它确实会,但由于“[-stroke](https://imagemagick.org/command-line-options/#stroke)”颜色不可见,你看不到它。你只能看到这条线 1 像素宽区域内部的“填充”。 |
magick -size 100x40 xc:lightblue \
-fill white -strokewidth 3 -draw "line 5,35 95,5" \
line_fill_3.jpg
![[IM 输出]](../static/img/draw/line_fill_3.jpg)
| 我实际上把上面的结果视为一个 bug。因为没有可填充的“区域”,也没有设置线条的“描边色”,所以本不该绘制任何东西。IM 目前这样做是为了避免让新用户困惑,但实际上只会给高级用户带来问题。更多细节请参见Draw 填充边界。
---|---
但如果同时定义了描边色,就会得到所要求的粗线…… |
magick -size 100x40 xc:lightblue \
-stroke black -strokewidth 3 -draw "line 5,35 95,5" \
line_stroke_3.jpg
![[IM 输出]](../static/img/draw/line_stroke_3.jpg)
如果“[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)”设置为 1,上面的线条会被完全覆盖。 |
magick -size 100x40 xc:lightblue \
-stroke black -strokewidth 1 -draw "line 5,35 95,5" \
line_stroke_1.jpg
![[IM 输出]](../static/img/draw/line_stroke_1.jpg)
当然,掌握了这些知识之后,你就可以像绘制字体时一样,拿它做出一些有创意的效果。 |
magick -size 100x40 xc:lightblue \
-stroke black -strokewidth 5 -draw "line 5,35 95,5" \
-stroke white -strokewidth 2 -draw "line 5,35 95,5" \
line_multi.jpg
![[IM 输出]](../static/img/draw/line_multi.jpg)
这里我使用了最细的“[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)”设置“0”,和上面处理字体时一样。 |
magick -size 100x40 xc:lightblue \
-fill white -stroke black -strokewidth 0 -draw "line 5,35 95,5" \
line_stroke_0.jpg
![[IM 输出]](../static/img/draw/line_stroke_0.jpg)
这会产生一种非常奇怪的点线效果,由黑点和灰色线段组成。这是描边色、填充色和背景色之间出现奇怪“色彩拍频”的结果。下面是这条线的放大视图…… |
magick -size 25x10 xc:lightblue \
-fill white -stroke black -strokewidth 0 -draw "line 2,8 22,1" \
-scale 400% line_stroke_0_white.jpg
![[IM 输出]](../static/img/draw/line_stroke_0_white.jpg)
| “色彩拍频”效果有点像两把吉他音高只有细微偏差时产生的“声音拍频”。在这里,当描边色完全覆盖下方的填充色时会得到黑点;当描边色同时与填充色和背景色混合时会得到灰点。 这种色彩混合是 IM 为改善线条和其他 draw 对象外观而使用的抗锯齿处理的自然结果。更多信息请参见我的 IM 中的抗锯齿讨论和示例页面。
---|---
注意,这种效果只出现在斜线中,不会出现在纯水平或纯垂直线中;在那里混叠不起作用,因此也没有“色彩拍频”效果。 |
magick -size 100x40 xc:lightblue \
-fill white -stroke black -strokewidth 0 -draw "line 5,20 95,20" \
line_stroke_horz.jpg
![[IM 输出]](../static/img/draw/line_stroke_horz.jpg)
这里我在放大视图中使用了不同的底层填充色,这样你可以看到颜色如何改变最终的拍频效果。 |
magick -size 25x10 xc:lightblue \
-fill none -stroke black -strokewidth 0 -draw "line 2,8 22,1" \
-scale 400% line_stroke_0_none.jpg
magick -size 25x10 xc:lightblue \
-fill red -stroke black -strokewidth 0 -draw "line 2,8 22,1" \
-scale 400% line_stroke_0_red.jpg
magick -size 25x10 xc:lightblue \
-fill black -stroke black -strokewidth 0 -draw "line 2,8 22,1" \
-scale 400% line_stroke_0_black.jpg
magick -size 25x10 xc:lightblue \
-fill black -stroke none -draw "line 2,8 22,1" \
-scale 400% line_stroke_-_black.jpg
![[IM 输出]](../static/img/draw/line_stroke_-_black.jpg)
如你所见,绘制非常细的线条时,可以通过使用相同的填充色和描边色,或者把其中一种颜色设为 none 来关闭它,从而减少这种“拍频”。后者是更好的想法,但前者对你的特定编程需求来说可能更实用。注意,填充线条的粗细为“0”。但描边线条可以有更大的粗细。它同样也是浮点值!2.5 像素宽的线条完全有效。 | 这些结果不仅是由有 bug 的描边宽度 0 引起的色彩拍频造成的,也因为在没有实际可填充区域时,“填充色”仍以额外 1.0 直径的粗细被绘制出来。我同样把这视为 bug。参见Draw 填充边界。
---|---
Draw 填充边界
关于各种绘图基元,还有几点需要注意。stroke-width 对大于 1.0 的浮点值工作良好,但在小于 1.0 的值上似乎会失效。这是由所用的实现算法造成的,并不只是因为它是错的,因为较粗的线条可以正常工作。基本上,如果使用零描边宽度,你可能会期望不会添加描边色。实际得到的却是一种拍频图案:当线条穿过像素实际“中心”时,描边色会以满强度出现。真正应该发生的是,添加到像素中的颜色量应反映被绘制线条的面积,而不是像素到该线条的距离。因此,零宽度线条不应向图像添加任何颜色,而小于 1.0 粗细的线条只应添加较少的颜色。请参见上面的使用 StrokeWidth 和 Stroke 绘制线条示例。另一个问题是,填充色并不是只应用到正在绘制的形状(多边形)边缘,而是会再向外延伸 ½ 像素。这包括未应用“描边”、边缘本应精确的情况。它也包括绘制“线条”的情况,因为线条实际上具有“零”的填充粗细。基本上,如果不启用描边就绘制线条,从技术上讲你不应看到线条,因为它没有“填充”粗细。相反,线条会至少带着 1 像素宽的“填充”色被绘制出来。这是出于历史原因,通常可以避免 IM 新用户困惑。可惜这对高级用户来说并不正确。这意味着,如果只使用填充色绘制两个共享一条边的多边形,由于每个多边形沿所有边都大了 ½ 像素,那条共享边会重叠 1 像素。换句话说,多边形和其他形状并不能彼此严丝合缝,而是会重叠!例如,这里我尝试用 draw 把一幅图像分成两半(在白色上绘制黑色)。为此,我绘制两个共享一条边且精确无重叠的多边形。生成的“小”图像已经放大以便显示。
magick -size 10x10 xc: -draw 'polygon 2,-1 7,10 10,10 10,-1' bound_left.gif
magick -size 10x10 xc: -draw 'polygon 2,-1 7,10 -1,10 -1,-1' bound_right.gif
magick bound_left.gif bound_right.gif -compose Plus -composite bound_add.gif
两个黑色部分(也就是实际绘制出来的部分)其实相互重叠了!换句话说,虽然我们尝试用绘制的多边形分别绘制两个区域,填充区域却比请求的范围略大。我还把两幅图像(通过 Plus 合成)相加,这样你就能实际看到绘制出的黑色区域的重叠。如果两个多边形完美吻合,“相加”后的图像应该是纯白色。实际重叠量等同于默认的“[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth) 1.0”设置。因此通常会预期这块额外区域会被普通的描边宽度覆盖。不过它确实可能造成一些实际问题。旁注:要完整测试接合,可以在黑色背景上生成 50% 灰色区域并把它们相加。这样不仅能看到这些区域是否“重叠”(如上所示),还能在相加时测试它们是否“欠重叠”(在填充区域之间留下间隙)。结果图像应该是完全平滑的 50% 灰色,并且沿接合处没有颜色变化。透明度检查则应在完全透明背景上使用 50% 透明、50% 灰色的颜色。若要查看基于单个蒙版图像进行完美切割并重新相加的示例,请参见合成方法示例 Compose DstOut。未来的 bug 修复:填充区域应该是精确的,但为了在绘制形状时对此进行补偿,默认“描边色”应设为填充色(除非它本身被明确设置)。
MVG - Magick Vector Graphics
上面展示的基元构成了所提供的所有“[-draw](https://imagemagick.org/command-line-options/#draw)”操作的基础。它们合在一起,是 ImageMagick 内部一种特殊语言的起点,这种语言称为 Magick Vector Graphics。关于该语言的更多细节,请参见 IM 网站上的 MVG 基元和语法概要。这个“MVG”语言的设计目标,是让 ImageMagick 能够处理更加复杂的 SVG(Scalable Vector Graphics)语言。它通过尝试把以 SVG 格式给出的图像转换为更简单的内部 MVG 格式来做到这一点。更多细节请参见下面的 SVG 处理。因此,上面看到的内容只是“[-draw](https://imagemagick.org/command-line-options/#draw)”运算符能力中很小的一部分。不过,如果想绘制复杂对象,我确实建议你使用像“[Sodipodi](http://the-labs.com/Sodipodi/)”这样的 SVG 编辑器,单独创建该对象的 SVG 格式图像。(参见下面的非 IM 矢量图形程序)。与 SVG 不同,MVG 没有任何形式的“容器”或图像命令集合。在转换过程中,这些都会被移除,从而生成一组简化后的 MVG 绘图命令序列。取而代之的是,它使用图形上下文这一概念来保存和恢复各种绘图设置,下面我们就来看这一点。
命令行设置与 MVG 设置
首先,几乎所有通过命令行选项设置、并由绘图基元使用的设置,在 MVG 绘图命令中都有直接对应项。通过命令行选项(例如“-strokewidth”)设置,与在 MVG 绘图字符串内部使用某个设置(例如“stroke-width”)之间的主要区别是,MVG 设置只在该 MVG 命令字符串持续期间有效。
**常规绘图设置概要**
__cmd_option__ __draw_MVG__ __参数__
-fill fill 图形内部的颜色/图块
-tile fill 图像图块,替代填充色
-stroke stroke 图形周围的线条颜色/图块
-strokewidth stroke-width 像素宽度
+antialias stroke-antialias 0/1 线条边缘混叠
-font font font_name / font_file
-family font-family ?
-weight ? ?
-stretch ? ?
-pointsize font-size 以点为单位的高度
-kerning - 额外字符间距
+antialias text-antialias 0/1 文本绘制混叠
-box text-undercolor 字体边界框的填充色
- decorate (None, Underline, LineThrough 或 Overline)
-gravity gravity (None, North, South-East,...)
-fuzz - 色彩差值 / 百分比
-bordercolor - color
注:
- 无此选项 ? 未知
| 这些设置通常都很好理解,因为它们在上面已经被经常使用和演示。 | font、stretch、style 和 weight 用于从 ImageMagick 字体列表中识别字体。不过大多数人只是选择一个具体的字体和 pointsize 来使用。因此它们在 IM 中很少用到。 |
|---|---|
如你所见,“颜色填充”基元的特殊设置在 MVG 中没有直接对应项。也就是“-bordercolor”和“-fuzz”因子设置。它们必须在使用“[-draw](https://imagemagick.org/command-line-options/#draw)”运算符之前从命令行指定。某些 MVG 设置作为全局命令行设置可能会更有用,例如用于字体绘制的“decorate”设置。警告:“-gravity”不是 SVG 规范的一部分。在 MVG 内,它只用于文本和图像的放置以及对齐。目前还没有与默认“重力”效果分离的对齐设置。不过由于对齐是 SVG 文本处理的一部分,这将来很可能会改变。现在,(位于 MVG 绘图字符串之外的)全局命令行设置会用于初始化你应用的每个“-draw”操作的设置,这就是为什么可以设置一个“-fill”颜色,然后用它绘制该颜色的圆。 |
|
| --- | |
magick -size 100x60 xc:skyblue -fill red \
-draw "circle 50,30 40,10" draw_circle_global.gif
![[IM 输出]](../static/img/draw/draw_circle_global.gif)
你可以在“-draw”MVG 参数内部,局部覆盖这个全局设置……
|
magick -size 100x60 xc:skyblue -fill red \
-draw "fill green circle 50,30 40,10" draw_circle_override.gif
![[IM 输出]](../static/img/draw/draw_circle_override.gif)
不过,在单个“-draw”MVG 参数内设置的设置,只在该“-draw”操作持续期间存在。也就是说,“-draw”内部的设置只对这一次 draw 局部有效,不会延续到后续单独的“-draw”参数中。
|
magick -size 100x60 xc:skyblue -fill red -draw 'fill green' \
-draw "circle 50,30 40,10" draw_circle_local.gif
![[IM 输出]](../static/img/draw/draw_circle_local.gif)
如果计划执行很多操作,最好把它们全部放在单个 MVG 字符串中完成,而不是使用多个“-draw”操作。
|
magick -size 100x60 xc:skyblue \
-draw "fill green circle 41,39 44,57
fill blue circle 59,39 56,57
fill red circle 50,21 50,3 " draw_circle_multi.gif
![[IM 输出]](../static/img/draw/draw_circle_multi.gif)
MVG 专用设置 即便在使用基元操作时,了解其他控制线条和对象绘制方式的 MVG 设置也很有用。这些包括……
__draw_MVG__ __说明/参数__
fill-opacity 填充透明度,范围 0.0 到 1.0
clip-rule 交叉线条的填充样式 (evenodd, nonzero)
stroke-opacity 线条透明度,0.0 到 1.0 的数值
stroke-dasharray 线条 'on' 与 'off' 长度列表
stroke-dash
stroke-linecap 线端外观:butt round square
stroke-linejoin 线条接合: butt miter round square
stroke-miterlimit 'miter' 接合变为 'bevel'(或 'butt')的角度
请记住,所有 MVG 设置和绘图运算符的完整列表,可以在 IM 网站上的 MVG 基元和语法概要 中看到。我们来看一些较简单设置的效果…… |
# Stroke Opacity
magick -size 100x60 xc:skyblue -fill none -stroke black \
-draw " path 'M 10,10 L 90,10'" \
-draw "stroke-opacity 0.8 path 'M 10,20 L 90,20'" \
-draw "stroke-opacity 0.6 path 'M 10,30 L 90,30'" \
-draw "stroke-opacity 0.4 path 'M 10,40 L 90,40'" \
-draw "stroke-opacity 0.2 path 'M 10,50 L 90,50'" \
set_stroke_opacity.gif
# Fill Opacity
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw " rectangle 5,10 15,50 " \
-draw "fill-opacity 0.8 rectangle 20,10 30,50 " \
-draw "fill-opacity 0.6 rectangle 35,10 45,50 " \
-draw "fill-opacity 0.4 rectangle 50,10 60,50 " \
-draw "fill-opacity 0.2 rectangle 65,10 75,50 " \
-draw "fill-opacity 0 rectangle 80,10 90,50 " \
set_fill_opacity.gif
# Plain and Dashed Lines
magick -size 100x60 xc:skyblue -fill none -stroke black \
-draw " path 'M 10,10 L 90,10'" \
-draw "stroke-dasharray 5 3 path 'M 10,20 L 90,20'" \
-draw "stroke-dasharray 5 5 path 'M 10,30 L 90,30'" \
-draw "stroke-dasharray 10 3 3 3 path 'M 10,40 L 90,40'" \
-draw "stroke-dasharray 1 6 path 'M 10,50 L 90,50'" \
set_lines.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw " path 'M 10,10 L 90,10'" \
-draw "stroke-dasharray 5 3 path 'M 10,20 L 90,20'" \
-draw "stroke-dasharray 5 5 path 'M 10,30 L 90,30'" \
-draw "stroke-dasharray 10 3 3 3 path 'M 10,40 L 90,40'" \
-draw "stroke-dasharray 1 6 path 'M 10,50 L 90,50'" \
set_lines_fill.gif
# Note: Technically the second image should be the same as the first
# as the 'filled' lines contain no area. This I regard as a BUG.
# Stroke Ends and Joins
magick -size 100x60 xc:skyblue -fill white -stroke black -strokewidth 8 \
-draw " path 'M 20,20 L 20,70'" \
-draw "stroke-linecap butt path 'M 40,20 L 40,70'" \
-draw "stroke-linecap round path 'M 60,20 L 60,70'" \
-draw "stroke-linecap square path 'M 80,20 L 80,70'" \
set_endcaps.gif
magick -size 100x60 xc:skyblue -fill white -stroke black -strokewidth 5 \
-draw " path 'M 5,70 L 20,20 35,70'" \
-draw "stroke-linejoin miter path 'M 35,70 L 50,20 65,70'" \
-draw "stroke-linejoin bevel path 'M 55,70 L 70,20 85,70'" \
-draw "stroke-linejoin round path 'M 75,70 L 90,20 105,70'" \
set_linejoin.gif
magick -size 100x60 xc:skyblue -fill white -stroke black -strokewidth 5 \
-draw " path 'M 5,70 L 20,20 35,70'" \
-draw "stroke-miterlimit 7 path 'M 35,70 L 50,20 65,70'" \
-draw "stroke-miterlimit 6 path 'M 65,70 L 80,20 95,70'" \
set_miterlimit.gif
![[IM 输出]](../static/img/draw/set_endcaps.gif)
![[IM 输出]](../static/img/draw/set_linejoin.gif)
![[IM 输出]](../static/img/draw/set_miterlimit.gif)
“stroke-miterlimit”设置相当难演示。这个属性定义“miter”接合在什么角度变为“bevel”接合。基本上,对于非常尖锐的角度,miter 会从两条线的实际接合处伸出很远。它会为这种尖锐程度设定一个上限,并在角点伸得太长时将其削钝。不过要注意,它表示的是某种角度的三角函数值,不是长度或距离。该值必须大于 1.0。上面展示的是,在我所显示的接合角度下,miter 会在 6 到 7 之间的某个值突然变成 bevel。例如,“stroke-miterlimit”为 1.414 时,任何小于 90 度的角都会把“miter”转换为“bevel”。值 4.0(默认值)会在角度小于约 29 度时转换接合;而值 10.0 会在角度小于约 11.5 度时转换。 SVG 路径绘制 SVG 路径是 SVG 的基本绘图基元。它用于绘制线条、图形、圆、曲线、弧等。SVG Paths 的完整规范可在 SVG 路径规范 文档中找到。不过这个文档并不好读,因为它实际上是写给程序员而不是普通用户的,所以我会把路径规范简化并概括如下……
- 字母是命令,而所有数字(浮点数)都是参数。
- 可以用逗号或空格作为参数分隔符,否则它们会被完全忽略。
- 每个路径组件的最后两个参数 (x,y) 会成为该路径组件的终点(或 'knot')。
- 大写字母指定最终点的绝对坐标。
小写字母相对于前一个组件的终点。
例如:“M 1,2 l 3,4 l 2,-4”与“M 1,2 L 4,6 L 6,2”相同。
也就是说,3,4 被加到 1,2 上,用来绘制一条到 4,6 的线。
然后再加上 2,-4,绘制到最终坐标 6,2 的线。 - 每个元素的参数可以通过继续添加更多数字参数组来重复,而无需再次写出同一个路径字母。不过对于曲线,我建议为了便于阅读还是把函数字母加上。
- “
M”或“m”的重复参数会分别按“L”或“l”处理。
例如:“M 1,2 3,4 5,6”与“M 1,2 L 3,4 L 5,6”相同
而:“m 1,2 3,4 2,-4”与“m 1,2 l 3,4 l 2,-4”相同 - 对于三次贝塞尔,所有点(控制点和终点 knot 点)都相对于前一个路径组件的终点给出。
注意,你可以用绝对坐标或相对坐标来指定内容。因此,可以用相对坐标定义一个对象,只提供一个初始的绝对“移动”坐标来放置整条路径。另一方面,你也可以使用其他“graphic-content”命令,在“viewbox”或“translation”中移动整幅绘图(见下文)。所以在 SVG 路径中使用绝对坐标还是相对坐标,实际上并不重要。 移动、线条和路径闭合 是学习 SVG 对象路径的起点。 |
# Open, Completed and Closed Paths (same points)
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 40,10 L 20,50 90,10 70,40'" path_open.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 40,10 L 20,50 90,10 70,40 40,10'" path_complete.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 40,10 20,50 90,10 70,40 Z'" path_closed.gif
![[IM 输出]](../static/img/draw/path_complete.gif)
![[IM 输出]](../static/img/draw/path_closed.gif)
不过要注意,“Z”只会闭合回路。它不会创建单独的对象。因此两个“闭合”的路径,无论相互重叠还是完全分离,仍然被归为一个绘制对象。 这里展示两个闭合但相互重叠、且按同一方向绘制的回路。由于只使用了单一路径,该对象就是单个对象,而“fill-rule ”设置控制重叠区域如何填充。 |
# Overlapping Paths and Fill Rule
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "fill-rule evenodd \
path 'M 40,10 20,20 70,50 Z
M 20,40 70,40 90,10 Z' " path_evenodd.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "fill-rule nonzero \
path 'M 40,10 20,20 70,50 Z
M 20,40 70,40 90,10 Z' " path_nonzero.gif
![[IM 输出]](../static/img/draw/path_evenodd.gif)
![[IM 输出]](../static/img/draw/path_nonzero.gif)
由于这些对象都是围绕中心按相同的角度方向绘制的,两个闭合环会围出一个循环值为 2 的区域。因此,'evenodd' 规则会让该区域保持未填充,而非零的 'nonzero' 规则会填充它。不过要注意,所有路径仍然可见,因为它们实际上是同一个对象。路径的绘制方向非常重要;一般来说,所有路径都应该相对于对象的“内部”以完全相同的方向绘制。比如这里,我把第二个对象按与第一个相反的方向绘制。于是当两个对象重叠时,该区域被绕过的次数为“零”次。也就是说,无论使用哪种 'fill-rule',它都不会被填充,从而形成一个“洞”。 |
# Overlapping Closed Objects, Second object drawn in reverse
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "fill-rule evenodd \
path 'M 40,10 20,20 70,50 Z
M 20,40 90,10 70,40 Z' " path_rvs_evenodd.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "fill-rule nonzero \
path 'M 40,10 20,20 70,50 Z
M 20,40 90,10 70,40 Z' " path_rvs_nonzero.gif
![[IM 输出]](../static/img/draw/path_rvs_nonzero.gif)
![[IM 输出]](../static/img/draw/path_rvs_evenodd.gif)
这意味着只要反转方向,并让对象的“内部”始终位于行进方向的同一侧,就可以在对象中生成“洞”。 |
# An object with a reversed drawn hole!
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,10 20,55 70,50 80,5 Z
M 50,20 60,40 40,30 Z' " path_with_hole.gif
![[IM 输出]](../static/img/draw/path_with_hole.gif)
由于这个洞同时是“even”和“zero”,所以不会填充,结果与 'fill-rule' 设置无关。当然,如果使用完全独立的 'path' 元素,就会生成完全独立的对象。在这种情况下,'fill-rule' 不适用,这些对象只是按照给定顺序彼此叠在上面绘制。 |
# Separate paths are separate objects
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 40,10 20,20 70,50 Z'
path 'M 20,40 70,40 90,10 Z' " path_separate.gif
未来:坐标对齐的路径 "H" 和 "V"
椭圆弧 是 SVG 路径中的圆形绘制功能...
'large' 和 'sweep' 参数尤其重要,因为它们用于决定该路径组件从起点到终点“画弧”的四种方式之一。两个标志 'large' 和 'sweep' 定义在该半径下哪一条弧会连接这两个点。 |
# Elliptical Arcs : A radius_x,y angle large,sweep x,y
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 30,15 0 0,0 70,20'" path_arc.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 30,15 0 0,1 70,20'" path_arc2.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 30,15 0 1,0 70,20'" path_arc3.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 30,15 0 1,1 70,20'" path_arc4.gif
![[IM 输出]](../static/img/draw/path_arc.gif)
![[IM 输出]](../static/img/draw/path_arc2.gif)
![[IM 输出]](../static/img/draw/path_arc3.gif)
![[IM 输出]](../static/img/draw/path_arc4.gif)
第二个标志 'sweep' 只是决定弧应该画在直线路径方向的哪一侧。'large' 标志用于选择更长的路径,即绕过椭圆中心的那条路径。也就是说,设置后的弧角度会大于 180 度。关闭它时,你会得到不包含椭圆中心、角度小于 180 度的较小“弧”。用 'Z' 闭合一段弧只会绘制最后一条直线段。要创建完整的椭圆或圆,至少需要两段 'arc' 线段:从第一个点到第二个点,再从第二个点回到第一个点。两段弧应使用相同的 'sweep' 设置,这样在行进方向不同的情况下,弧会位于不同侧。其中一段弧应打开 'large' 设置。 |
# Closed and angled elliptical arcs (defined by two edge points)
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 30,20 20 0,0 70,20 Z '" path_arc5.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 30,20 20 1,1 70,20 Z '" path_arc6.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 30,20 20 0,0 70,20 \
A 30,20 20 1,0 30,40 Z '" path_arc7.gif
![[IM 输出]](../static/img/draw/path_arc5.gif)
![[IM 输出]](../static/img/draw/path_arc6.gif)
![[IM 输出]](../static/img/draw/path_arc7.gif)
注意,如果这条线太长,无法在给定角度下放进指定的椭圆大小,椭圆的大小会被放大以适应该线,并让椭圆以这条线为中心。这意味着,通过为轴半径使用较小的数值,你可以只指定轴长比例,并保证这条直线路径穿过椭圆的中心点。也就是说,该路径会形成一条从椭圆一侧到另一侧的椭圆直径。它不一定是椭圆的长轴或短轴,只是一条椭圆直径。 |
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 3,2 45 0,0 70,20'" path_arc_x.gif
![[IM 输出]](../static/img/draw/path_arc_x.gif)
当然,使用 "1,1" 作为长度会得到一个完美的半圆,从一个点到下一个点。在这种情况下,椭圆角度不会造成任何差别。 |
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 1,1 0 0,0 70,20'" path_hcircle.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 1,1 0 0,0 70,20
A 1,1 0 1,0 30,40 Z'" path_circle.gif
![[IM 输出]](../static/img/draw/path_circle.gif)
SVG 对 'arc' 的定义还声明,如果两个半径中的任意一个为零,就应该绘制直线。因此,任何半径为 "0,0" 的弧都只是简单的直线弧... |
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 0,0 0 0,0 70,20'" path_arc_line.gif
![[IM 输出]](../static/img/draw/path_arc_line.gif)
如果为弧指定非常大的半径,并且没有为返回路径指定 'large sweep',就可以在两个点之间创建具有该半径的透镜形状。 |
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 30,40 A 50,50 0 0,0 70,20
A 50,50 0 0,0 30,40 Z'" path_lens.gif
![[IM 输出]](../static/img/draw/path_lens.gif)
这种弧是一个关键功能。它可以让原本是直线的线条很容易地带上一点小但明显的弯曲。例如,不用像下面这样的简单三角形... |
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 20,55 L 25,10 L 70,5 L 20,55 Z' " triangle.gif
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 20,55 A 100,100 0 0,0 25,10
A 100,100 0 0,0 70,5
A 100,100 0 0,0 20,55 Z' " triangle_curved.gif
![[IM 输出]](../static/img/draw/triangle_curved.gif)
线条的端点没有变化,发生的只是每个 'L' 都被替换成了一段弧。不过弧的大小应该与线条长度成比例。由于我没有这样做,那条较长的对角线比另外两条有强得多、深得多的弯曲。请记住,在调整尺寸或缩放正在绘制的对象时,也应该按与该线条长度相同的比例缩放半径,使弯曲相应调整,弧也能正确缩放。注意,'sweep' 标志会根据每个路径线段的绘制方向,控制曲线向外鼓出还是向内凹入(见上文)。 |
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 20,55 A 100,100 0 0,0 25,10
A 100,100 0 0,1 70,5
A 100,100 0 0,1 20,55 Z' " triangle_bulge.gif
![[IM 输出]](../static/img/draw/triangle_bulge.gif)
看起来“静态”的直边三角形,现在有点像被风鼓起的帆。如果你确实想让线完全变直,而又不把它们转换回真正的线段,可以把弧半径设为零来关闭曲线。因此,弧不仅适合生成椭圆和圆,也很适合绘制直线以及略微弯曲的线段。它是一种非常通用的点到点绘制路径。生成彼此分离的弯曲线段时,一个简单的替代办法是不用椭圆弧,而使用二次贝塞尔线段。主要区别在于,它用单个控制点而不是圆半径来定义弧。这也让你可以把弧偏向线段的一端,但代价是更难生成对称的弧。当然,你也可以把两者“混用”。 饼图示例 作为使用弧的收尾,我们来看一个用它们生成圆形扇区的例子。当然,你可能需要使用一些外部三角函数数学(你的高中数学还好吗?)来确定所需的路径终点。 |
magick -size 140x130 xc:white -stroke black \
-fill red -draw "path 'M 60,70 L 60,20 A 50,50 0 0,1 68.7,20.8 Z'" \
-fill green -draw "path 'M 60,70 L 68.7,20.8 A 50,50 0 0,1 77.1,23.0 Z'" \
-fill blue -draw "path 'M 68,65 L 85.1,18.0 A 50,50 0 0,1 118,65 Z'" \
-fill gold -draw "path 'M 60,70 L 110,70 A 50,50 0 1,1 60,20 Z'" \
-fill black -stroke none -pointsize 10 \
-draw "text 57,19 '10' text 70,20 '10' text 90,19 '70' text 113,78 '270'" \
piechart.jpg
![[IM 输出]](../static/img/draw/piechart.jpg)
注意所有弧都绘制在“线条路径”的左侧,并相应地(使用 'sweep' 标志)设置了标志。但如果弧覆盖的角度大于 180 度,就需要设置 'large' 标志;见上例最后的 'gold' 部分。还要注意,应该把每个区段完整绘制出来,即使这意味着你可能得把边界线画两次。如果不这样做,你很可能要么无法用颜色完全填满该区段,要么填充颜色会覆盖先前绘制区段的轮廓。避免多条线重复的唯一办法是先绘制所有填充区域,然后再重复一次绘制轮廓。也就是说,你需要把所有内容画两遍,并确保它们正确对齐。因此,轮廓线重叠大概是最简单的解决方案。 三次贝塞尔 曲线可以用 'C' 函数定义,该函数指定两个控制点和最终终点。对于使用上一个控制点的镜像(以保持连续曲线)的连续三次贝塞尔曲线,可以使用 'S' 函数。下面是一个例子。由于这个函数比较复杂,我预先准备了一块画布,显示控制点的位置,以及上一个控制点的“假定镜像”。 |
# Cubic Bezier: C control_1_x,y control_2_x,y x,y
# Smooth " : S control_2_x,y x,y
magick path_cubic_canvas.gif -fill white -stroke black \
-draw "path 'M 10,30 C 10,4 50,4 50,30 S 90,55 90,30' " \
path_cubic.gif
![[IM 输出]](../static/img/draw/path_cubic.gif)
连接控制点与该路径线段最终路径点的线(控制线)基本上定义了曲线经过该路径点时的方向。较长的控制线会在该点产生更平滑的曲线,较短的线则会在该点产生更尖锐的曲线。如果控制点与曲线上的点重合(控制线长度为零),曲线会在该点出现尖锐的不连续,就像只使用了直线段一样。作为更实用的例子,下面这段代码摘自 IM Examples Logo 生成脚本,它创建 IM Examples Logo 中弯曲的飞溅区域。这个例子比较巧妙的地方在于,我用 magick 将所用的三次贝塞尔路径字符串转换成另一条路径,用来显示生成贝塞尔曲线所用的控制线。这样我就能看到曲线控制线的角度和长度,调整结果也容易得多。只需要调整一组点,就能同时显示曲线和控制线,从而把错误降到最低。 |
curve="M 12,27 C 7,37 18,50 18,60 S 0,80 10,94
S 40,74 50,78 S 60,99 76,95 S 72,70 75,65
S 95,55 95,42 S 69,37 66,32 S 67,2 53,7
S 43,17 35,22 S 17,17 12,27 Z"
c_ctrls=`echo $curve | \
sed '1s/\([0-9]\) *\([0-9]\)/\1 M \2/;
s/S/M/g; s/C/ /;' -`
magick -size 100x100 xc:white \
-draw "stroke None fill Green path '$curve'" \
-draw "stroke Red fill None path '$c_ctrls'" \
curvy_splash.gif
![[IM 输出]](../static/img/draw/curvy_splash.gif)
如果仔细看图,你会看到曲线的起点和终点有两条方向相反的控制线。对于闭合的连续路径,起点和终点的控制线都应处于相同角度(只是镜像方向),当然长度也应相同。这一点很容易弄错,因此很重要。沿曲线的其他所有点都只有一个控制点/控制线,它指向与曲线绘制方向 相反 的方向。该线段越长,该控制点处的曲线就越不“尖锐”;长度为零则会产生一个“尖点”。'S' 函数会在内部根据前一个线段的数据为下一个线段生成镜像控制点/控制线,从而产生平滑延续的曲线。有关此路径函数的更多示例,请参见 SVG:三次贝塞尔曲线命令。手动生成贝塞尔曲线 不需要花哨的 GUI 工具,也相对直接。
- 首先定义你希望曲线经过的所有坐标点,并在列表末尾重复起始坐标。
- 然后扩展这个列表,把所有 x,y 坐标点复制为成对的点,并在每一对前添加一个 '
S'(Smooth Cubic)函数。每一对中的第一个数是控制点,它连接到第二个数所表示的曲线上的点。不过第一对点的含义相反,第一个点是曲线起点,第二个点表示第一个也是唯一一个反向控制点。 - 将第一对坐标的函数字母从 '
S' 改为 'M',然后在这一对坐标之间添加一个 'C'。最后从第二对坐标中删除 'S',完成初始的三次('C')函数。 - 通过添加最后的 '
Z' 来闭合曲线,完成该路径。
请参见上面的示例序列,了解它应该是什么样子。 - 到这一步,你可以测试绘制这条路径。由于所有控制线的长度都是零,路径只会由直线段组成。
- 现在你只需要缓慢而仔细地调整控制线段的位置(每个 '
S' 对中的第一个坐标),直到得到想要的最终曲线。不要让控制线太长,也不要方向错误,否则会得到一条看起来很滑稽的曲线。 - 为了帮助查看改动并发现错误,可以使用上面的转换 "
sed" 命令,在路径控制点和曲线控制点之间绘制控制线。不过要注意,长度为零的控制线不可见,但由于这条线会产生尖点,其位置应该很明显。 - 最后确保 '
C' 之后的第一个控制点/控制线,在同一位置上与末端控制点/控制线正好相反。
交互式曲线生成 也可以通过一些矢量图形编辑器完成。例如 Luis Guerra 报告说,"Inkscape" 生成的贝塞尔曲线可以通过 "Edit -> XML Editor" 功能访问:先使用该功能,然后选择你想查看控制点的路径或形状。
你知道其他用 GUI 工具提取贝塞尔曲线的方法吗(为曲线上的每个点给出两个或一个控制点)?或者有其他生成这种曲线的技巧?请给我发邮件!我很想听听。和其他人一样,你会因这个技巧获得署名。
二次贝塞尔 是三次贝塞尔函数的一种简化形式,即两个控制点合并为单个控制点的情况。同样,你可以用 'Q' 函数开始曲线,然后用 'T' 函数延续曲线,镜像上一个控制点。 |
# Quadratic Bezier: Q control_x,y x,y
# Smooth " : T x,y
magick path_quad_canvas.gif -fill white -stroke black \
-draw "path 'M 10,30 Q 20,4 50,30 T 90,30' " \
path_quad.gif
![[IM 输出]](../static/img/draw/path_quad.gif)
不过我应该提醒你,'T' 延续函数实际上只适用于连接等间距点的路径。我不推荐使用它。二次曲线的优势在于可以替代椭圆弧,因为它使用实际位置而不是半径来定义弧。它还可以让弧偏向一端而不是另一端,而这一点在使用椭圆弧时并不实用。 |
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "path 'M 20,55 Q 30,32 25,10
Q 50,1 70,5
Q 50,45 20,55 Z' " triangle_bulge_2.gif
![[IM 输出]](../static/img/draw/triangle_bulge_2.gif)
在这种情况下,弧并不那么均匀,得到的形状更像倒置的鲨鱼鳍,而不是帆。请记住,二次弧是抛物线,而椭圆弧基本上生成圆形线段。这可能是决定应使用哪种弧形线段的关键。有关此路径函数的更多示例,请参见 SVG:二次贝塞尔曲线命令。
绘图表面的变形
在这些能力之外,还可以用各种方式变形绘制对象所在的绘图表面,从而做出一些很有意思的效果。首先可以应用一些通用的绘图表面修改,例如... 'translate'、'rotate'、'scale'、'skewX'、'skewY' 和 'affine'。例如,给定一个由线组成的 'path',我们可以把绘图表面的原点或 0,0 点“translate”到另一个位置。 |
magick -size 100x60 xc:skyblue \
-draw "translate 50,30
image over 3,3 0,0 'terminal.gif'
fill white stroke black
path 'M 0,20 -45,20 20,-25 -25,-25'
fill none stroke red
path 'M 0,10 0,-10 M 10,0 -10,0' " transform_translate.gif
![[IM 输出]](../static/img/draw/transform_translate.gif)
请注意,绘图区域的 '0,0' 或原点现在位于图像中心,不过 Y 轴仍然是图像顶部为负、底部为正。"rotate" 操作会旋转绘图表面,因此之后在该表面上绘制的任何内容都会以旋转后的方式绘制。当然,它会围绕平移后的原点旋转,所以最好把这两个变换运算符一起使用。 |
magick -size 100x60 xc:skyblue \
-draw "translate 50,30 rotate -30
image over 4,4 0,0 'terminal.gif'
fill white stroke black
path 'M 0,20 -45,20 20,-25 -25,-25'
fill none stroke red
path 'M 0,10 0,-10 M 10,0 -10,0' " transform_rotate.gif
magick -size 100x60 xc:skyblue \
-draw "translate 50,30 scale 1.5,1.5
image over 4,4 0,0 'terminal.gif'
fill white stroke black
path 'M 0,20 -45,20 20,-25 -25,-25'
fill none stroke red
path 'M 0,10 0,-10 M 10,0 -10,0' " transform_scale.gif
![[IM 输出]](../static/img/draw/transform_scale.gif)
"scale" 的一个常见用途是翻转 Y 轴,使正的 Y 值向上。当然,为了让坐标保持有序,原点也应该移动到中心或左下角。 |
magick -size 100x60 xc:skyblue \
-draw "translate 50,30 scale 1,-1
image over 4,4 0,0 'terminal.gif'
fill white stroke black
path 'M 0,20 -45,20 20,-25 -25,-25'
fill none stroke red
path 'M 0,10 0,-10 M 10,0 -10,0' " transform_flip.gif
![[IM 输出]](../static/img/draw/transform_flip.gif)
最后,"skewX" 和 "skewY" 会在 X 和 Y 方向上切变图像。例如,这里我们使用 "skewX" 给图像的垂直 Y 轴一个倾斜。 |
magick -size 100x60 xc:skyblue \
-draw "translate 50,30 skewX 20
image over 4,4 0,0 'terminal.gif'
fill white stroke black
path 'M 0,20 -45,20 20,-25 -25,-25'
fill none stroke red
path 'M 0,10 0,-10 M 10,0 -10,0' " transform_skewY.gif
![[IM 输出]](../static/img/draw/transform_skewY.gif)
这些运算符在 MVG "[-draw](https://imagemagick.org/command-line-options/#draw)" 字符串之外也有等价形式,可用于一般用途。不过这些命令行版本是运算符,会立即应用到内存中已经存在的图像上,而不是只应用到还未绘制矢量对象的绘图表面。更多细节请参见扭曲图像。
绘图表面的仿射变形
上面五种画布变换都可以组合成一个通用的仿射矩阵运算符。可以使用 MVG 原语 'affine',也可以先用 "[-affine](https://imagemagick.org/command-line-options/#affine)" 设置仿射变换,然后再调用 "[-draw](https://imagemagick.org/command-line-options/#draw)"。仿射变换使用一组“矩阵系数”,用来定义你给出的坐标应如何修改为实际绘图坐标。关于这些“系数”实际如何工作,更多细节请参见仿射矩阵变换。例如... 若只是为后续绘制的对象设置一个中心原点... |
magick -size 100x60 xc:skyblue \
-draw "affine 1,0,0,1,50,30
image over 4,4 0,0 'terminal.gif'
fill white stroke black
path 'M 0,20 -45,20 20,-25 -25,-25'
fill none stroke red
path 'M 0,10 0,-10 M 10,0 -10,0' " affine_null.gif
magick -size 100x60 xc:skyblue \
-draw "affine 1,0,0,-1,50,30
image over 4,4 0,0 'terminal.gif'
fill white stroke black
path 'M 0,20 -45,20 20,-25 -25,-25'
fill none stroke red
path 'M 0,10 0,-10 M 10,0 -10,0' " affine_flip.gif
magick -size 100x60 xc:skyblue \
-draw "affine .866,-.5,.5,.866,50,30
image over 4,4 0,0 'terminal.gif'
fill white stroke black
path 'M 0,20 -45,20 20,-25 -25,-25'
fill none stroke red
path 'M 0,10 0,-10 M 10,0 -10,0' " affine_rot.gif
![[IM 输出]](../static/img/draw/affine_rot.gif)
对于更复杂的仿射变换,可以使用为此目的创建的仿射辅助脚本。这些脚本会用 magick 将旋转角度和中心点等内容转换成可直接用于 "[-draw](https://imagemagick.org/command-line-options/#draw) affine" 或 "[-affine](https://imagemagick.org/command-line-options/#affine)" 设置的仿射坐标。
Push/Pop 上下文
一些 MVG 原语实际上依赖这些变换才能正确使用。例如,椭圆原语只能直接用正交对齐的轴来指定。 |
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "ellipse 50,30 30,15 0,360" ellipse_orthogonal.gif
![[IM 输出]](../static/img/draw/ellipse_orthogonal.gif)
不过使用绘图变换,我们可以很容易地给椭圆添加一个“旋转角度”。 |
magick -size 100x60 xc:skyblue -fill white -stroke black \
-draw "push graphic-context
translate 50,30 rotate 30
fill white stroke black
ellipse 0,0 30,15 0,360
pop graphic-context" ellipse_rotated.gif
![[IM 输出]](../static/img/draw/ellipse_rotated.gif)
注意,椭圆的 'center'(旋转点)先被平移,然后才应用旋转。随后 'ellipse' 在平移后的位置 '0,0' 处绘制。上面的例子还展示了两个新的 MVG 绘图原语:'push graphic-context' 和 'pop graphic-context'。在上面的例子中它们并非严格必需,但在进行较大的绘图变换时建议使用。'push' 和 'pop' 原语的作用是保存当前绘图状态,也就是 'graphic-context',随后再恢复它。两个原语之间更改的任何绘图设置都会被遗忘。这包括任何表面变形,比如 'translate' 和 'rotate',也包括颜色设置 'fill' 和 'stroke',或其他任何修改绘图“状态”的内容。这些原语让你可以很容易地绘制带有许多变换的非常复杂的对象,然后把一切恢复到更“正常”的情况,以便进行后续绘图操作。你可以在下面的绘制箭头中看到一个更实用的演示。
Push/Pop 特殊对象
建设中
更多专用于 MVG 处理 SVG 格式的设置。
font-family font-stretch font-style font-weight
encoding 'UTF-8'
push defs
push gradient 'def_name' linear X1,Y1 X2,Y2
stop-color 'color' where
stop-color 'color' where
# where is a point between the two pixels given (0 = X1,Y1 1= X2,Y2)
gradient-units 'objectBoundingBox|userSpaceOnUse'
affine ....
pop gradient
push gradient 'def_name' radial CX,CY FX,FY R
# Here CX,CY is the center of the radial gradient of radius R
# the FX,FY is the focal, and is usually the same a CX,CY
# unless you are trying to warp the gradient in a specific direction
stop-color 'color' where
...
pop gradient
pop defs
push graphic-context
fill 'url(#def_name)'
... draw things here ...
pop graphic-context
示例可参见 _Florent Monnier's_ 的开发站点...
<http://www.linux-nantes.fr.eu.org/~fmonnier/OCaml/MVG/>
读取 MVG 文件 从上面的示例可以看到,MVG "-draw" 参数可能会变得很长。事实上,SVG 转换为 MVG 可能生成一些极长的 MVG 绘图参数(见下文)。不过,IM 的通用命令行接口允许你使用“@filename”参数从文件读取任意字符串参数。这很方便,因为你可以把很长、很复杂的 MVG 绘图命令放在单独文件中读取。例如,如果我把 MVG 操作放入名为“draw_circles.mvg”的文件,就可以这样绘制…… |
magick -size 100x60 xc:skyblue -draw @mvg_circles.mvg mvg_draw.gif
| ![[IM 输出]](../static/img/draw/mvg_draw.gif)
不止如此,ImageMagick 还知道如何直接读取 "MVG:" 图像文件格式,让你可以更直接地绘制这类命令。不过,除非 MVG 文件定义了画布,否则你可能需要指定它要绘制到的初始画布("-size" 和 "-background")。 |
magick -size 100x60 -background limegreen mvg_circles.mvg mvg_file.gif
| ![[IM 输出]](../static/img/draw/mvg_file.gif)
你可以向 MVG 文件添加“viewbox”,并配合适当的背景色填充绘制,把初始画布设置移入 MVG 图像中。这样 MVG 图像文件就成为一个完整的图像定义。 |
magick mvg_circles2.mvg mvg_image.gif
| ![[IM 输出]](../static/img/draw/mvg_image.gif)
| _目前,从 MVG 参数字符串内部读取外部 MVG 文件只有一种方法,就是使用“image”绘图图元。不幸的是,这会在把该图像叠加到绘图表面之前,先把 MVG include 转换为栅格图像。
换句话说,目前没有 MVG 的“include”函数。_ :-(
---|---
建设中
你可以使用 "[+render](https://imagemagick.org/command-line-options/#render)" 记录 IM 的低级绘图操作。
当随后给出 "[-render](https://imagemagick.org/command-line-options/#render)" 设置/运算符时,IM 会立即绘制那些已保存的
操作。
奇怪的是,仅仅输出到 "MVG" 文件似乎也会这样做...
magick ... -draw '....' draw_commands.mvg
注意:如果在输出 MVG 格式文件时绘制曲线,该文件会把曲线列成
一系列短线段,而不是原始曲线。
当然,你也可以更彻底一些,使用更通用的 SVG 格式。
参见下面的 "[SVG 格式处理](#svg)"。
MVG Alpha 合成
建设中
我还没有见过在对象绘制中使用 Alpha 合成(除了 'painters' algorithm,
它基本上是一种 'over' Alpha 合成)的情况。
不过,这并不是说它不能做到。
如果你想用不同的 Alpha 合成方式(例如 'DstOver',一种类似 Under 的
合成方式)来合成矩形、椭圆、圆形或其他图形,
那就先在一个与原图同尺寸的空白透明画布上绘制图形,
再把它合成到图像上。
不过,由于 SVG 允许你使用 Alpha 合成把文本和其他项目绘制到图像上,
我想这将来会被加入。
敬请期待!
绘制符号
有时你在图像上有一组点,想在这些点上绘制十字、圆等参考符号。遗憾的是,目前 IM 没有能轻松绘制这类符号的命令,但稍微多做一点额外工作就可以画出来。
符号绘制技巧
在给定位置列表上绘制多个符号的诀窍,是用 shell 脚本或你正在使用的 API 生成 MVG 绘图命令,把给定的一组点转换成相应的一组绘图命令。例如,这里我把一串点变成每个点上的“加号”…… |
# Define a string of X and Y coordinates
# comma between values, space between coordinates.
points="6.6,7.7 25.0,75.0 42.2,85.4 75.8,94.7 51.5,39.3 92.5,66.6"
# magick each point into a draw command for a cross (using 'awk')
# the 'tr' converts spaces into 'newlines' (one point per line).
crosses=`echo $points | tr -s ' ' '\012' |\
awk -F, '{ print "line " $1-3 "," $2 " " $1+3 "," $2 ;
print "line " $1 "," $2-3 " " $1 "," $2+3 ; }' -`
# draw a red line between the points, and blue crosses on the points.
magick -size 100x100 xc:white \
-draw "fill none stroke red polyline $points " \
-draw "fill none stroke blue $crosses " \
points_plus.gif
![[IM 输出]](../static/img/draw/points_plus.gif)
上面用“tr”将每个点(两个数字)分成一行一个点,然后用“awk”完成在给定点上绘制“加号”所需的所有数学计算。这里你可以用任何喜欢的东西,因为我只是对输入点列表做一种文本宏展开。几乎任何编程语言都能做到。对于上面的 shell 脚本,我只是发现“awk”是最简单也最快的方法。实际上,你甚至可以使用 ImageMagick 本身,通过“magick”的格式选项来完成那种“宏”展开…… 例如,这里我用它为这个“点符号”计算圆周上的一个点。 |
# Define a string of X and Y coordinates
# comma between values, space between coordinates.
points="6.6,7.7 25.0,75.0 42.2,85.4 75.8,94.7 51.5,39.3 92.5,66.6"
# circle radius (or symbol size) to draw around each point.
radius=3.5
# magick each point into a draw command for a cross
# In this case, points are space separated by the shell
circles=$(for point in $points; do
x=$(echo "$point" | cut -d, -f1)
y=$(echo "$point" | cut -d, -f2)
# use IM to do some floating point math, EG: y2=$y+$radius
y2=$(magick xc: -format '%[fx:'"$y"'+'"$radius"']' info:)
echo "circle $x,$y $x,$y2"
done)
# Draw a red line between the points, and blue circles on the points.
magick -size 100x100 xc:white \
-draw "fill none stroke red polyline $points " \
-draw "fill none stroke blue $circles " \
points_circle.gif
![[IM 输出]](../static/img/draw/points_circle.gif)
现在,生成的 draw 字符串可能会相当长,并可能开始让最终命令的长度出问题。因此,与其把点转换成长字符串再在命令行上传给 IM,不如把 draw 命令作为文件通过管道传给 IM。这次我还使用 SVG 路径绘制方法,而不是绘制图元的绘制方法。另外,我生成的符号是围绕每个点的三角形。 |
# Define a string of X and Y coordinates
# comma between values, space between coordinates.
points="6.6,7.7 25.0,75.0 42.2,85.4 75.8,94.7 51.5,39.3 92.5,66.6"
# magick each point into a draw commands to draw a triangle
for point in $points; do
echo "path 'M $point m 0,-5 -4,+8 +8,0 -4,-8'"
done |\
magick -size 100x100 xc:white \
-fill none -stroke red -draw "path 'M $points' " \
-fill none -stroke blue -draw '@-' \
points_tri.gif
![[IM 输出]](../static/img/draw/points_tri.gif)
SVG 路径实际上让这件事更容易,因为它允许相对像素移动;这样你可以设计符号,使它在给出用于绘制符号的一系列“移动”和“直线”之前,只需要一个初始的绝对移动“M”。因此你完全不需要浮点计算,因为 IM draw 会完成所需的定位数学。 | 相对移动SVG 路径项“m”在 IM v6.4.3-5 之前是坏的。如果你的 IM 比这个版本更旧,上面(以及下一个)示例可能什么也画不出来。对于旧版本,可以把上面的相对移动“m”替换为适当的一系列相对直线“l”来修正。
---|---
现在还可以再进一步,把一个完整的 MVG 文件(包含绘图画布规格)作为绘图命令管道直接送入 IM。这次我们做一个“十字”,它类似上面第一个“加号”示例,但那个示例需要大量计算。 |
# Define a string of X and Y coordinates
# comma between values, space between coordinates.
points="6.6,7.7 25.0,75.0 42.2,85.4 75.8,94.7 51.5,39.3 92.5,66.6"
# Generate a MVG file for IM to draw all components
( echo "viewbox 0 0 100 100 fill white rectangle 0,0 100 100"
echo "fill none stroke red path 'M $points'"
echo "fill none stroke blue path '"
for point in $points; do
echo " M $point m -2,-2 +4,+4 m -4,0 +4,-4"
done
echo "'"
) | magick mvg:- points_cross.gif
![[IM 输出]](../static/img/draw/points_cross.gif)
这里使用了一种特殊的 shell 编程技巧:在 shell 括号中“echo”出来的任何内容,都会作为 MVG 文件送入最后的“magick”命令。第一个“echo”定义并填充图像的绘图画布,而“while”循环会把给定的每个“point”转换为给定半径的圆。这种方法的优点是,不会遇到另外两种方法可能遇到的字符串长度限制。你还可以生成的其他符号包括方框、菱形、误差棒等…… 另请参阅下面的“绘制圆”,其中介绍其他圆形方法,包括无需计算的相对“path”圆形绘制。
绘制符号的替代方法
除了直接绘制之外,还有其他方法可以把符号添加到图像中。
符号字体
你可以从符号字体中提取符号,并把它们保存为小位图。也可以为此使用预先定义的小型彩色图像。不过,这种方法在相对于特定像素精确定位字体时可能会有困难。也就是说,它并不是一种很精确的技术。但你可以把任何图像合成到任何像素位置。例如,这些符号就是从若干字体中提取出来,专门用于这些示例页面的。
把图像合成到更大背景上的示例见图像分层一节。不过,循环方法可能更有用,例如分层图像的程序化定位中给出的做法。未来:使用坐标对图像进行分层的示例
形态学
另一种做法是使用形态学来“膨胀”单个像素,所用的是“[Disk](morphology.html#disk)”、“[Ring](morphology.html#ring)”和“[Plus](morphology.html#plus)”这类特殊的“形状”核,甚至也可以使用你自己的用户定义核。例如……
magick -size 80x80 xc:black -fill white \
-draw 'point 20,15 point 55,30 point 40,60' points_pixels.gif
magick points_pixels.gif -morphology Dilate Ring points_rings.gif
magick points_pixels.gif -morphology Dilate Plus:4 points_pluses.gif
magick points_pixels.gif -morphology Dilate Cross:3 points_crosses.gif
随后可以使用 Alpha Shape 运算符 直接把结果转换成彩色叠加图像。它最大的优点是,你实际上不需要知道每个符号的具体位置,也不需要知道符号有多少个。但这也可能成为缺点。一个主要缺点是位置只能是整数位置。你不能使用浮点的“子像素”定位来“绘制”。
卷积
几乎相同的技术是配合专门设计的核使用“Convolve”,这样可以设置不同深浅的灰度,而不是像上面那样只有简单的开/关结果。通过为图像的每个通道(红、绿、蓝和 alpha)使用不同的用户定义核,甚至可以从每个像素坐标创建多色符号。
为此,我使用自己写的特殊脚本“[image2kernel](../static/img/scripts/image2kernel)”,把彩色图像(见右侧)转换为每个通道各自独立的浮点卷积核。
image2kernel -q marker.png marker.dat
这会生成四个文件,例如“[marker_R.dat](../static/img/draw/marker_R.dat)”,这个非常小的输入图像的每个通道一个。它们是该图像的用户定义表示(原点位于图像中心)。现在,使用这些核数据文件,我们可以把那些单点 Convolve 成透明背景上的彩色标记图像。
magick points_pixels.gif -alpha off \
\( -clone 0 -morphology Convolve @marker_R.dat \) \
\( -clone 0 -morphology Convolve @marker_G.dat \) \
\( -clone 0 -morphology Convolve @marker_B.dat \) \
\( -clone 0 -morphology Convolve @marker_A.dat \) \
-delete 0 -channel RGBA -combine point_markers.png
| IM v6.7.6-9 之前,Combine 运算符要求图像的透明度通道以“opacity”值而不是 alpha 值给出,因此生成的 alpha 通道需要取反。例如: |
... "`cat marker_A.dat`" -negate \) \
只应使用小图像,并让像素点分布得足够开,避免符号相互重叠。这是因为 Convolve 会把重叠区域相加,使它们比预期更亮。上面的做法已经转换成 UNIX shell 脚本“[convolve_image](../static/img/scripts/convolve_image)”,以便使用起来更容易。
convolve_image points_pixels.gif marker.png point_markers.png
这种技术来自 IM Forums 上的讨论 A Fun Experience with IM。那位用户想把小人放在一张足球场背景图上,让它们在图中的位置拼出某个人的名字。
分层
另一种技术,例如使用从源图像中提取的像素列表来定位的图像图层,可能是更好的办法。你可以先叠加更远处的符号图像,再叠加前景图像,并且可以通过程序选择或随机决定哪个符号替换哪个点。此示例见地图上的图钉。
绘制圆
draw 选项提供了多种方式来做一件非常基本的事……绘制圆。例如,你可以绘制一个通过圆周上任意点的圆。因此你需要计算一个中心点,以及离第一个点一个半径距离(比如 25 像素)的第二个点。 |
magick -size 100x60 xc: -stroke Firebrick -fill tomato -strokewidth 2 \
-draw 'circle 50,30 50,55' circle_circle.gif
![[IM 输出]](../static/img/draw/circle_circle.gif)
Fred Weinhaus 指出,通过使用平移,可以不再需要计算圆边缘坐标,而是直接给出半径。 |
magick -size 100x60 xc: -stroke SeaGreen -fill PaleGreen -strokewidth 2 \
-draw 'translate 50,30 circle 0,0 25,0' circle_circle_trans.gif
![[IM 输出]](../static/img/draw/circle_circle_trans.gif)
不过,在绘制多个圆时,上面的方法要么需要为每个圆单独执行“[-draw](https://imagemagick.org/command-line-options/#draw)”操作,要么使用上下文压栈。使用椭圆时,可以直接把半径指定为轴长 |
magick -size 100x60 xc: -stroke Sienna -fill Wheat -strokewidth 2 \
-draw 'ellipse 50,30 25,25 0,360' circle_ellipse.gif
![[IM 输出]](../static/img/draw/circle_ellipse.gif)
你也可以通过使用“stroke-linecap round”绘制一条非常非常短的线来生成圆。描边宽度随后会设置圆的直径。注意,这条线必须有一定长度(无论多小),否则 draw 什么也不会画。 |
magick -size 100x60 xc: -stroke Blue -strokewidth 50 \
-draw 'stroke-linecap round line 50,30 50,30.0001' \
circle_line.gif
![[IM 输出]](../static/img/draw/circle_line.gif)
遗憾的是,这种技术不能为生成的圆描边,但在覆盖大面积时,较大的描边宽度会很有用。见下面的几个简单示例。这种方法使用 SVG 路径绘制方法,因此无需计算任何额外坐标即可绘制圆。 |
magick -size 100x60 xc: -stroke Blue -fill DodgerBlue -strokewidth 2 \
-draw "path 'M 50,30 m 0,25 a 1,1 0 0,0 0,-50 a 1,1 0 1,0 0,50'" \
circle_path.gif
![[IM 输出]](../static/img/draw/circle_path.gif)
只需要初始的绝对移动“M”来定义中心,后续路径组件中的“25”和“50”定义相对于该中心的圆半径和直径。 | 相对移动SVG 路径项“m”在 IM v6.4.3-5 之前是坏的。如果你的 IM 比这个版本更旧,圆可能只显示为一个像素。对于旧版本,可以把上面的“m”替换为“l”来修正。
---|---
Fred Weinhaus 添加了下面的贝塞尔圆方法。它非常接近真正的圆(尽管并不精确),并且需要浮点计算。 |
r=25; cx=50; cy=30;
x1=25; x2=75; # = cx ± radius
y1=-3.25; y2=63.25; # = cy ± radius*1.275
magick -size 100x60 xc: -stroke Purple -fill Violet -strokewidth 2 \
-draw "bezier $x1,$cy $x1,$y1 $x2,$y1 $x2,$cy" \
-draw "bezier $x1,$cy $x1,$y2 $x2,$y2 $x2,$cy" \
circle_bezier.gif
![[IM 输出]](../static/img/draw/circle_bezier.gif)
如果绘制精确圆并不重要,可以使用这个由 4 个贝塞尔段组成的 SVG 路径,它只使用圆的 X 和 Y 边界进行计算。 |
r=25; cx=50; cy=30;
x1=25; x2=75; # X bounds = cx ± radius
y1=5; y2=55; # Y bounds = cy ± radius
magick -size 100x60 xc: -stroke Tomato -fill Gold -strokewidth 2 \
-draw "path 'M $cx,$y1 Q $x1,$y1 $x1,$cy T $cx,$y2 $x2,$cy $cx,$y1 z'" \
circle_bezier_path.gif
![[IM 输出]](../static/img/draw/circle_bezier_path.gif)
如果你想要一个完全相对于中心起点绘制的版本,可以使用这种技术。它只使用半径值,因此仅用 API 中的字符串函数就很容易生成。 |
magick -size 100x60 xc: -stroke Orange -fill LemonChiffon -strokewidth 2 \
-draw "path 'M 50,30 m 0,25 q 25,0 25,-25 t -25,-25 -25,25 25,25 z'"\
circle_bezier_path_rel.gif
绘制箭头 -- 定位、旋转和缩放符号
使用上述技术,你可以创建一种特殊符号,例如箭头头部,并将其定位为尖端位于一条线的最末端,然后画在其上。如果在线之后绘制箭头(典型情况),箭头就会画在线的上方。不过,可定义的箭头有三类,每一类都会根据用途以不同方式定义。
- 测量:你只是想用箭头头部标记线段两端,以指示某个工程图中的测量界限。非常简单。
- 矢量:显示某个值的方向和强度。
例如天气风向图。它需要尾部,0,0 点是尾部的末端。通常会创建一大片这样的矢量网格。 - 指示符:指出某个细节。
为此,0,0 点大概应该是尖端,或在箭头本身前方一小段距离的位置。
测量箭头
只需在线的末端加一个箭头头部,这相对容易。基本上你创建一个“箭头头部”符号,并把它画在正确位置。例如…… |
arrow_head="l -15,-5 +5,+5 -5,+5 +15,-5 z"
magick -size 100x60 xc: -draw 'line 10,30 80,30' \
-draw "stroke blue fill skyblue
path 'M 80,30 $arrow_head' " \
arrow_horizontal.gif
![[IM 输出]](../static/img/draw/arrow_horizontal.gif)
注意,我把符号画成其起点正好是线的末端。这样它就可以向后画在先前绘制的线之上,形成非常漂亮整洁的符号。不过箭头带有方向。你可以创建大量不同角度的箭头定义,很多程序也这样做。但箭头是矢量,那么为什么不把箭头当作矢量来旋转呢?IM draw 命令内置了绘图旋转(画布扭曲),所以我们就用它。这还具有一个优点:可以把位置从箭头头部的“path”定义中移出来,让你把整条路径指定为一个“常量”…… |
arrow_head="path 'M 0,0 l -15,-5 +5,+5 -5,+5 +15,-5 z'"
magick -size 100x60 xc: -draw 'line 25,55 70,10' \
-draw "stroke blue fill skyblue
translate 70,10 rotate -45
$arrow_head
" \
arrow_rotate.gif
![[IM 输出]](../static/img/draw/arrow_rotate.gif)
如果想改变箭头大小,请在 rotate 后添加一个 "scale" draw 选项。 |
arrow_head="path 'M 0,0 l -15,-5 +5,+5 -5,+5 +15,-5 z'"
magick -size 100x60 xc: -draw 'line 25,55 70,10' \
-draw "stroke blue fill skyblue
translate 70,10 rotate -45 scale 2,2
$arrow_head
" \
arrow_scale.gif
![[IM 输出]](../static/img/draw/arrow_scale.gif)
注意它是如何放大的,同时把箭头的“尖端”留在你指定的位置。这是处理箭头时非常重要的一点,因为真正重要的只是你要添加箭头的那条线的终点和角度。“变换”的顺序很重要,而且实际执行顺序正好相反。也就是说,先把 scale 应用到坐标,然后 rotate,最后 translate。如果坐标变换不是按这个顺序完成,最终放置箭头的位置也会被缩放,箭头就不会出现在我们期望的位置。另外,由于 scale 有两个数字,而原始箭头头部符号是按水平(角度为零)设计的,你可以分别缩放箭头的宽度和高度。还要注意,描边宽度也随箭头头部大小一起缩放,从而保持一致。 |
arrow_head="path 'M 0,0 l -15,-5 +5,+5 -5,+5 +15,-5 z'"
magick -size 100x60 xc: -draw 'line 25,55 70,10' \
-draw "stroke blue fill skyblue
translate 70,10 rotate -45 scale 2,1
$arrow_head
" \
arrow_scale_aspect.gif
![[IM 输出]](../static/img/draw/arrow_scale_aspect.gif)
现在,因为你是在扭曲画布来绘制各个箭头,而且也许还夹杂许多其他绘制操作,你可能希望把它们全部放在一次 "[-draw](https://imagemagick.org/command-line-options/#draw)" 操作中完成。比如先画线,然后在两端都加上箭头,这需要不同的颜色、位置、旋转,甚至可能还需要不同的缩放。这意味着我们必须把画布扭曲的作用范围限制在绘制每一个箭头头部的部分。如果不限制这个范围,后续其他绘制操作也可能受到影响,你也就无法完全确定最终生成的是什么。要限制扭曲(以及所有其他绘制属性)的范围,可以把相关部分包在一个 "graphic-context" 中…… |
arrow_head="path 'M 0,0 l -15,-5 +5,+5 -5,+5 +15,-5 z'"
magick -size 100x60 xc: \
-draw "stroke black fill none
path 'M 10,40 A 50,50 0 0,1 90,40'
push graphic-context
stroke blue fill skyblue
translate 10,40 rotate 135
$arrow_head
pop graphic-context
push graphic-context
stroke firebrick fill tomato
translate 90,40 rotate 45
$arrow_head
pop graphic-context
" \
arrow_context.gif
![[IM 输出]](../static/img/draw/arrow_context.gif)
'push' 本质上会把当前所有绘制属性保存起来,以备之后使用,而 'pop' 会恢复这些属性,用先前保存的设置替换当前的各种设置(颜色、扭曲、位置等)。这意味着在执行 'pop' 后,“画布扭曲”被取消,draw 又回到修改之前的状态。上面的技术只是生成箭头的一种方法;当把箭头作为距离测量的一部分来绘制时(例如技术图纸中),它很合适。
矢量箭头
如前所述,矢量同时表示某个值的方向和强度。这意味着箭头长度是可变的,箭头头部也可能位于离矢量起点任意距离的位置。你当然可以做一堆数学计算,根据矢量的长度和角度算出箭头头部应该放在哪里,但有一个更好的办法:让 ImageMagick 替你完成这些计算。解决办法是在扭曲后的画布空间中,把矢量长度画成一条正确长度的水平线。画完这条线后,在画布仍然处于“扭曲”状态时,只需再次把绘制空间平移到线的末端。这样你就处在正确的位置,并带有正确的旋转,可以像平常一样绘制矢量的“箭头头部”。例如,这里我生成一个 70 像素长、角度为 -35 度的矢量。 |
vector_head="path 'M 0,0 l -15,-5 +5,+5 -5,+5 +15,-5 z'"
indicator="path 'M 10,0 l +15,+5 -5,-5 +5,-5 -15,+5 m +10,0 +20,0 '"
magick -size 100x100 xc: \
-draw "stroke black fill none circle 20,50 23,50
push graphic-context
stroke blue fill skyblue
translate 20,50 rotate -35
line 0,0 70,0
translate 70,0
$vector_head
pop graphic-context
push graphic-context
stroke firebrick fill tomato
translate 20,50 rotate 40
$indicator
translate 40,0 rotate -40
stroke none fill firebrick
text 3,6 'Center'
pop graphic-context
" \
arrow_with_tails.gif
指示箭头
上面我还演示了一个指向前一个矢量箭头起点的指示箭头。不过我没有像之前那样画箭头,而是把它做成一个反向箭头符号,从距离原点(或起点)10 个像素的位置开始。也就是说,符号位于我要指示的位置,而我并不希望箭头正好压在那个位置上,而是离它稍远一点。指示箭头比矢量更容易处理,通常不需要可变长度,但通常你会想在指示箭头的远端添加文本,用来说明它指示的是什么。和前面一样,计算那个位置可能很麻烦,所以没必要硬算。文本定位的解决办法也和矢量一样:保留绘制指示箭头时使用的原始扭曲空间,并把原点平移到该箭头的尾端(在扭曲空间中水平方向 40 像素)。现在位置已经重新调整,我们可以围绕这个新位置把扭曲“反向旋转”回来,这样就能像平常一样绘制文本(带一点偏移)。遗憾的是,虽然上例中默认的 'left' 文本对齐可以工作,但目前无法在 MVG 中把文本对齐作为独立于 gravity 的设置来指定。如果这对你有影响,请到 IM bugs 论坛提出请求;希望文本对齐(与 gravity 定位分开)最终能成为现实,尤其是它实际上已经是 SVG 规范的一部分。
绘制对象
宽大的颜色描边
你不必用路径或轮廓把填充区域完全围起来,才能创建各种形状。使用非常粗、非常宽的描边,你可以在画布上生成大块区域和色块。例如,一个宽描边的椭圆弧就能生成一块不错的颜色区域,我确实见过这种效果用于海报制作。 |
magick -size 100x100 xc: -fill none -stroke powderblue \
-draw 'stroke-width 70 ellipse -30,0 90,90 10,50' \
-rotate 180 arc_background.gif
magick -size 100x100 xc: \
-draw 'fill none stroke-linecap round
stroke-width 40 stroke tomato ellipse 50,0 70,70 65,115
stroke-width 2 stroke black ellipse 50,0 70,70 60,120
stroke-width 40 stroke palegreen line 50,40 50,40.01' clown.gif
圆柱体
在一次 IM 论坛讨论 中,大家围绕如何使用 ImageMagick draw 命令绘制圆柱体,尤其是带阴影的圆柱体,进行了深入讨论。绘制圆柱体的诀窍是绘制 'roundrectangle' 图元,让两端形成椭圆。也就是说,如果圆柱体宽 50 像素,就分别把矩形圆角设为 25 和 12 像素。换言之,先取矩形宽度的一半,再取一半。这样圆柱体就只是两个相互叠放的圆角矩形。第二个较浅颜色填充的“端部椭圆”,尺寸正好是两个圆角尺寸的两倍。例如…… |
magick -size 60x100 xc:white -stroke snow4 \
-fill chartreuse3 -draw 'roundrectangle 5,5 55,95 25,12' \
-fill chartreuse2 -draw 'roundrectangle 5,5 55,29 25,12' \
cylinder.gif
![[IM 输出]](../static/img/draw/cylinder.gif)
把第一个填充颜色替换为渐变阴影(使用内存中平铺技术)后,可以让圆柱体看起来更有一点 3D 感…… |
magick -size 60x100 xc:white -stroke snow4 \
\( -size 1x60 gradient:chartreuse1-chartreuse4 -rotate -90 \
-write mpr:shading +delete \) \
-tile mpr:shading -draw 'roundrectangle 5,5 55,95 25,12' +tile \
-fill chartreuse2 -draw 'roundrectangle 5,5 55,29 25,12' \
cylinder_shade.gif
![[IM 输出]](../static/img/draw/cylinder_shade.gif)
通过逐步细化圆柱体绘制(正如 IM 论坛中讨论的那样),你可以生成非常复杂且视觉效果很好的圆柱体。这包括添加包围在外的半透明玻璃圆柱、阴影效果和标签。那次讨论的最终结果是一个脚本 "[cylinder_bar](../static/img/scripts/cylinder_bar)",用来生成圆柱百分比条……
cylinder_bar 95 cylinder_95.png
该脚本可以生成任意尺寸的图像,并根据这个尺寸以及脚本顶部定义的其他设置,适当地调整所有参数。它还包含“玻璃厚度”的概念,用来在外层半透明玻璃圆柱和内部彩色圆柱之间留出间隙。请注意圆柱体上非常细微的阴影,尤其是绿色圆柱末端与玻璃圆柱端部重叠时!只要稍微预先想一想,就能做到令人惊讶的效果。
绘制文本字符串中的特殊字符
引号还是反斜杠?
人们在使用 -draw 时遇到的最大问题之一,是绘制那些对 UNIX shell、DOS 命令行,甚至 C、Perl、PHP、R 或 Visual Basic 等其他语言也具有特殊含义的字符。其中最麻烦的是两类引号字符,以及像美元符号 '$' 这样的变量替换字符,还有 shell 和 ImageMagick 的转义字符:反斜杠 '\'。基本上,传给 "[-draw](https://imagemagick.org/command-line-options/#draw)" 的 MVG 参数需要加引号,而且 其中的 'text' 字符串参数也可能还需要额外加引号。为了解决这个问题,用户通常会使用两种不同的引号字符:一种用于 shell,另一种用于 MVG 文本字符串。
`**-draw '... text 0,0 "string" ...'**`
注意,对 Windows 用户而言,这实际上是唯一可行的选择,因为 Windows 有自己的引号问题和处理方式。另一种做法是把引号对调,使用……
**-draw "... text 0,0 'string' ..."**
这样就可以包含 shell 变量替换(使用未转义的 '$')。选择正确的形式可以解决大多数问题,但有些字符仍然麻烦,而且每种解决办法都取决于你具体使用哪一组引号,因为它们也决定特殊字符应该如何转义。下面是四种引号组合以及特殊字符的处理方式……
- shell 参数使用单引号,
MVG 文本字符串外使用双引号。处理 draw 文本字符串最简单的技巧,是对外层 shell 参数使用单引号。不过这也意味着,如果要在绘制出的字符串中包含撇号,就必须离开 shell 的“单引号模式”,并在 shell 单引号之外提供这个撇号。例如,下面展示如何处理我提到的四个特殊字符。 |magick -size 250x50 xc:none -box white -pointsize 20 -gravity center \ -draw 'text 0,0 " '\'' \" $ \\ " ' \ -trim +repage text_special_sd.gif
![[IM 输出]](../static/img/draw/text_special_sd.gif)
注意,因为美元符号不需要转义,所以你也不能用它来替换 shell 变量的内容。务必记住,反斜杠是 IM draw 字符串会处理的_唯一_特殊字符。而且它存在的理由只是让你能够转义任何“IM draw 字符串引号”,例如我们上面用于双引号的做法。除此之外,所有其他怪异之处都来自 UNIX 命令行 shell,而不是 IM。PC-DOS 有它自己的怪异之处;如果有人能补充在该环境中使用 IM 时如何转义特殊字符,我会很感谢。
* shell 参数使用双引号,
MVG 文本字符串外使用单引号。如果你确实想把一个“shell 变量”插入绘制出的字符串,那么外层 shell 参数就必须使用双引号。这会让整个事情复杂得多,因为你失去了 shell 的保护,现在不仅要转义美元符号 '$',还要转义反斜杠 '\'。另一方面,shell 这时不需要把单引号字符用作参数结束分隔符,所以这一点会简化。让我们总结一下这几个特殊字符的结果。 |
magick -size 250x50 xc:none -box white -pointsize 20 -gravity center \
-draw "text 0,0 ' \\' \" \$ \\\\ ' " \
-trim +repage text_special_ds.gif
![[IM 输出]](../static/img/draw/text_special_ds.gif)
请注意,如果你想绘制反斜杠本身,MVG 文本字符串需要把反斜杠加倍(如前一个示例),但 shell 本身也需要把每一个反斜杠再加倍,于是为了生成一个这样的字符,总共需要四个反斜杠。这种加倍很快就会变得难以承受、也很让人困惑,需要大量反斜杠才能达到目的。慢慢来,别急,你会弄清楚自己场景中该怎么写。
* shell 参数使用单引号,
MVG 文本字符串外使用单引号。最后,我们用最后两种引号组合的总结来收尾。它们如何被 shell 和 MVG 解码,就留给你来推导。 |
magick -size 250x50 xc:none -box white -pointsize 20 -gravity center \
-draw 'text 0,0 '\'' \'\'' " $ \\ '\'' ' \
-trim +repage text_special_ss.gif
![[IM 输出]](../static/img/draw/text_special_ss.gif)
* shell 参数使用双引号,
MVG 文本字符串外使用双引号。 |
magick -size 250x50 xc:none -box white -pointsize 20 -gravity center \
-draw "text 0,0 \" ' \\\" \$ \\\\ \"" \
-trim +repage text_special_dd.gif
如你所见,从命令行传给 "[-draw](https://imagemagick.org/command-line-options/#draw)" 的参数,既要处理命令行 shell,又要处理 MVG 文本字符串内部的反斜杠和引号转义。结果可能让人困惑又棘手。只要记住,shell 会区别对待两种引号,而 MVG 文本字符串不会。当然,在复杂脚本中,更好的办法也许是完全避开 shell 和各种脚本问题。你可以从一个 MVG 绘制文件中读取 "[-draw](https://imagemagick.org/command-line-options/#draw)" 参数来做到这一点。
`**-draw @drawfile.mvg**`
当然,你仍然需要对你正在使用的任何引号字符加反斜杠,也要对文本中的任何反斜杠加反斜杠。不过,这比同时处理 shell 自己的引号和转义系统以及 IM 的系统要简单得多。 |
magick -size 500x50 xc:lightblue -font Candice -pointsize 36 \
-gravity center -draw @text_quotes.mvg text_quotes.gif
![[IM 输出]](../static/img/draw/text_quotes.mvg.gif)
![[IM 输出]](../static/img/draw/text_quotes.gif)
第一张图像来自我使用的一个 "MVG" 文本文件。它不包含任何 shell 转义或引号。因此,里面只有 MVG 的引号和转义。请注意,在上面的例子中,如果我对 MVG 文本字符串使用单引号,唯一的变化就是需要对字符串中的单引号字符加反斜杠,而不是对双引号字符这样做。关于百分号字符 关于 "-draw text" 运算符中的特殊“转义”字符,还有最后一点。百分号字符 '%' 应该“原样”绘制。你不需要为了绘制它们做任何特殊处理。如果它们不能“原样”绘制,说明你使用的是旧版 IM,应该尽快升级。 | 直到 IM 6.2.4 版本为止,'%' 字符曾作为转义字符,用于在绘制的文本字符串中包含额外的图像信息。现在已经不再如此,因为这类转义在 SVG 图像也试图绘制百分号字符时既令人困惑又不正确。
---|---
这种百分号“转义”的用法(以及 '\n' 换行转义)被认为与 "[-draw](https://imagemagick.org/command-line-options/#draw)" 运算符,以及 MVG 格式用于处理 SVG 图像格式的设计用途不兼容。因此从 IM 6.2.4 版本起,% 转义不再工作,反斜杠只会转义它自身和周围的引号。 |
magick -size 250x50 xc:none -box white -pointsize 20 -gravity center \
-draw 'text 0,0 "%w\n%h"' -trim +repage text_escapes.gif
![[IM 输出]](../static/img/draw/text_escapes.gif)
关于“百分号 bug”的更多细节,以及在较旧 ImageMagick 中使用 "[-draw](https://imagemagick.org/command-line-options/#draw)" 时避免它的方法,请参见绘制百分号 Bug页面。用 Annotate 代替 Draw 避免这类问题的更好办法,是在绘制文本时使用 "-annotate" 而不是 draw。这个运算符是 draw 运算符的包装器,可以使用 draw 的全部能力,但形式更简单。基本上,这个运算符只需要一组引号(用于 shell)。这让处理特殊字符简单得多得多。遗憾的是,虽然你不再需要为 IM 转义引号,但现在会遇到百分号转义,例如 '@' 文件读取、'\n' 换行,以及其他百分号转义展开。例如,使用单引号…… |
magick -size 200x50 xc:none -box white -pointsize 20 -gravity center \
-annotate 0 '\@ '\'' " $ \\ %% ' \
-trim +repage annotate_s.gif
magick -size 200x50 xc:none -box white -pointsize 20 -gravity center \
-annotate 0 "\@ ' \" \$ \\\\ %% " \
-trim +repage annotate_d.gif
![[IM 输出]](../static/img/draw/annotate_d.gif)
不过,如果你使用 '@' 转义从文件读取字符串,所有 annotation 引号和转义都会被完全忽略。例如,这里我们包含一张图像宽度和高度的信息! |
magick -size 200x50 xc:none -box white -pointsize 20 -gravity center \
-annotate 0 '%w\n%h' -trim +repage annotate_percents.gif
![[IM 输出]](../static/img/draw/annotate_percents.gif)
不过,从文件读取 annotation 字符串时,所有转义都会被完全忽略。 |
echo -n '@ %w\n%h' |\
magick -size 200x50 xc:none -box white -pointsize 20 -gravity center \
-annotate 0 '@-' -trim +repage annotate_file.gif
![[IM 输出]](../static/img/draw/annotate_file.gif)
更多信息请参见 Annotate 文本绘制运算符,尤其是 Annotate 转义字符。
IM 和 SVG 处理
SVG 输入驱动:RSVG 与 MSVG
处理真正的 SVG 图像格式是一件非常复杂的事。引擎需要处理 SVG -- Scalable Vector Graphics 文档所定义的方方面面。这需要大量编程工作和时间。因此,在处理 SVG 格式图像时,ImageMagick 提供了两种方法。第一种是使用开源 RSVG 库,把 SVG 格式 magick 转换为 IM 能轻松处理的栅格图像。这个引擎在 SVG 处理的几乎所有方面都很完整。第二种方法是让 IM 试着使用一个名为 MSVG 的内置方法,把 SVG magick 转换为 MVG。MSVG 会尝试把 SVG 图像 magick 转换为 IM "-draw" 运算符所用的 "MVG" 绘制语言。draw MVG 的许多功能正是为此目的创建的。遗憾的是,虽然基本的线条绘制和着色已经具备,但它远远称不上完整的 SVG 转换器。你可以使用特殊输入格式 "MSVG:"(IM v6.3.4 添加)读取 SVG 图像,来强制使用内部 MSVG 转换器。但如果 RSVG 库存在,大多数 ImageMagick 会改用它来渲染 SVG 图像。要查看你的 IM 会怎么做,可以使用……
magick -list format | grep SVG
从括号中的 "RSVG" 可以看出,我自己的 IM 会使用我电脑上安装的 RSVG 库,并显示其版本。这里我“绘制”一张小的手写 SVG 图像 "diagonal.svg"(由论坛用户 penciledin 贡献),它在白色背景上创建了一个带简单对角渐变的矩形。 |
magick diagonal.svg diagonal_rsvg.gif
![[IM 输出]](../static/img/draw/diagonal_rsvg.gif)
很好。生成了正确的对角渐变。不过,如果你使用内部 MSVG 来渲染它(当 RSVG 库不存在时的默认方式)…… |
magick msvg:diagonal.svg diagonal_msvg.gif
![[IM 输出]](../static/img/draw/diagonal_msvg.gif)
如你所见,内部 MSVG 转换失败了,返回的是垂直渐变,而不是对角渐变。你还可以通过把 SVG 直接转换为 MVG 文件,查看 IM 实际生成的 MVG 命令。
magick msvg:diagonal.svg mvg:diagonal.mvg
你大概可以看出,MSVG 转换器是如何尝试把 SVG magick 转换成 MVG 绘制命令的。当前内部 MSVG 已知会失败的情况包括……
- 非垂直渐变(不会转换为新的 MVG 渐变处理)
- 沿曲线路径排列的文本
- 文本对齐(独立于 gravity 的设置)
不过,大多数基本绘制动作都能处理。还要记住,MVG 语言实际上可以处理一些 SVG 无法处理的东西,包括使用 gravity 来定位图像和文本。Gravity 不是 SVG 规范的一部分,但它是 IM 文本和字体处理中的组成部分。还要记住,MVG 没有 SVG 那样的容器机制。内部 MSVG 转换器会用图形上下文的 push 和 pop(见上面的 MVG 输出)替代 XML 容器,效果是一样的。
SVG 设置
SVG 图像格式是一种矢量格式(见关于矢量图像格式的一点说明),因此图像通常没有默认“尺寸”。相反,它会像 postscript 一样,在某个特定的 "[-density](https://imagemagick.org/command-line-options/#density)" 下被“绘制”或“渲染”(默认 density 为 72 dpi)。另外,如果 SVG 没有“绘制”背景,你可以使用 "[-background](https://imagemagick.org/command-line-options/#background)" 设置指定要使用的背景颜色。例如,下面是另一张小 SVG 图像 "[home.svg](../static/img/images/home.svg)",它使用 3 种不同的 density、3 种不同背景进行了“渲染”,其中包括透明背景。
magick -density 36 home.svg home_1.gif
magick -background skyblue home.svg home_2.gif
magick -density 144 -background none home.svg home_3.png
请注意,我在上例中较大的透明背景版本使用的是 PNG 格式图像。由于存在半透明边缘像素,它生成的图像比 GIF 格式图像更干净。只要最终图像涉及透明度,始终推荐使用 PNG。 | _我发现有些 SVG 图像不会缩放。也就是说,它们是用“像素”定义的,而不是用“点”、“英寸”或“毫米”等真实世界长度定义的。因此,虽然 "[-density](https://imagemagick.org/command-line-options/#density)" 设置可能会改变图像整体尺寸(以真实世界单位计),但“像素”的大小不会改变,所以图像本身也不会改变大小。不过这类 SVG 图像相当少见。
更糟的是,少数 SVG 图像会混用“像素”和“点”度量;除非作者是故意这样做,否则当你尝试以不同于作者意图的 density 使用它时,可能会得到一团糟的结果。这种情况幸运地更少见。
一个简单修复通常只是把 SVG 中所有“pixels”单位改成“points”,但不应盲目这样做,因为使用“pixels”也可能是有意为之。_
---|---
SVG 输出处理
从 IM v6.4.2 起,IM 可以把任何位图图像 magick 转换成 SVG 矢量图形!转换并不总是成功,但较大和/或较简单的图像(例如位图遮罩)通常会 magick 转换得很好。例如,这里我把一个糟糕的位图形状 magick 转换为 SVG 图像,然后再 magick 转换回来,以便把位图平滑成正确的抗锯齿形状。
magick -pointsize 72 -font Candice label:A -threshold 50% \
-trim +repage -bordercolor white -border 5x5 A.gif
magick A.gif A.svg
magick A.svg A.png
| | | ![[IM 文本]](../static/img/draw/A.svg.gif)
不过,要让这起作用,必须安装“development” "[AutoTrace](http://autotrace.sourceforge.net/)" 库,并且 IM 要使用 "--with-autotrace" 开关配置。如果没有安装 "[AutoTrace](http://autotrace.sourceforge.net/)" 库,或它没有编译进 IM,那么生成的 SVG 输出会是大量单像素圆,得到二值结果,而不是平滑的 SVG 轮廓图像。这样的图像相比之下非常巨大,而且 SVG 渲染器往往需要很长时间来渲染。实际上需要一种更好的默认栅格转矢量技术,可能使用 Morphology skeleton 和 MAT 技术。曾经有一个 "autotrace:' 输入委托,用于“平滑输入位图图像”,它会直接使用 "autotrace" 命令一次完成上述所有步骤。不过上次我查看时,这个委托已经消失了。当时可以这样使用它…… |
magick autotrace:A.gif A_traced.png
![[IM 输出]](../static/img/draw/A_traced.png)
当然,这不会让你得到 "autotrace" 命令的 SVG 输出,只是让输入图像经由 SVG 过滤以把它平滑。作为替代,你也可以直接使用 "autotrace" 命令,如光栅到矢量边缘和使用 Autotrace 的骨架示例所示。你也可能想看看 cancerberosgx 在生成 SVG 图像中给出的结果,他研究的是照片转换方案。
非 IM 矢量图形编辑器
| ImageMagick 是一个像素数组处理器,通常不会保存矢量图像('MVG' 是唯一例外),只会读取它们并把它们 magick 转换为像素数组。其他像素图像编辑器也是如此,例如 Gimp、Photoshop 等。要编辑和处理基于矢量的图像,请使用如下程序 Sodipodi | 基于 SVG 的矢量图形编辑器 |
|---|---|
| Xfig | 简单但很好用的矢量对象编辑器 |
| (非常适合标志、地图以及在页面上排列照片) | |
| Dia | |
| AutoTrace | 将位图数组中的形状转换为矢量轮廓 |
| Sketch | 基于 Python 的矢量编辑器,支持曲线文本。 |
| 当然,这不是完整列表。即使是 OpenOffice、Word 和 TeX 等许多文字处理器,通常也带有各种简单但往往不好用的对象编辑器。不过,如果只是把一种矢量图形格式转换为另一种矢量格式,不要使用 ImageMagick。ImageMagick 本质上是、也永远会是一个栅格图像或位图图形的转换器和处理器。更多信息请参见关于矢量图像格式的一点说明。 |
![[IM 输出]](../static/img/draw/color_replace.png)
![[IM 输出]](../static/img/draw/color_floodfill.png)
![[IM 输出]](../static/img/draw/matte_floodfill.png)
![[IM 输出]](../static/img/draw/rose_raw.png)
![[IM 输出]](../static/img/draw/rose_gamma.png)
![[IM 输出]](../static/img/draw/stroke_font.jpg)
![[IM 输出]](../static/img/draw/stroke_table.jpg)
![[IM 输出]](../static/img/draw/stroke_thick.jpg)
![[IM 输出]](../static/img/draw/stroke_outline.jpg)
![[IM 输出]](../static/img/draw/line_stroke_0_none.jpg)
![[IM 输出]](../static/img/draw/line_stroke_0_red.jpg)
![[IM 输出]](../static/img/draw/line_stroke_0_black.jpg)
![[IM 输出]](../static/img/draw/bound_left_mag.gif)
![[IM 输出]](../static/img/draw/bound_right_mag.gif)
![[IM 输出]](../static/img/draw/bound_add_mag.gif)
![[IM 输出]](../static/img/draw/set_stroke_opacity.gif)
![[IM 输出]](../static/img/draw/set_fill_opacity.gif)
![[IM 输出]](../static/img/draw/set_lines.gif)
![[IM 输出]](../static/img/draw/set_lines_fill.gif)
![[IM 输出]](../static/img/draw/path_separate.gif)
![[IM 输出]](../static/img/draw/path_hcircle.gif)
![[IM 输出]](../static/img/draw/triangle.gif)
![[IM 输出]](../static/img/draw/transform_rotate.gif)
![[IM 输出]](../static/img/draw/affine_null.gif)
![[IM 输出]](../static/img/draw/affine_flip.gif)
![[IM 输出]](../static/img/draw/points_rings.gif)
![[IM 输出]](../static/img/draw/points_pluses.gif)
![[IM 输出]](../static/img/draw/points_crosses.gif)
![[IM 输出]](../static/img/draw/point_markers.png)
![[IM 输出]](../static/img/draw/circle_bezier_path_rel.gif)
![[IM 输出]](../static/img/draw/arrow_with_tails.gif)
![[IM 输出]](../static/img/draw/arc_background.gif)
![[IM 输出]](../static/img/draw/clown.gif)
![[IM 输出]](../static/img/draw/cylinder_95.png)
![[IM 输出]](../static/img/draw/text_special_dd.gif)
![[IM 输出]](../static/img/draw/annotate_s.gif)
![[IM 文本]](../static/img/draw/svg_handling.txt.gif)
![[IM 文本]](../static/img/draw/diagonal.svg.gif)
![[IM 文本]](../static/img/draw/diagonal.mvg.gif)
![[IM 输出]](../static/img/draw/home_1.gif)
![[IM 输出]](../static/img/draw/home_2.gif)
![[IM 输出]](../static/img/draw/home_3.png)
![[IM 输出]](../static/img/draw/A.png)