Wait the light to fall

ImageMagick - Drawing

焉知非鱼

ImageMagick Drawing

https://imagemagick.org/Usage/draw/

在 IM 中绘图是在现有图像中添加新元素的方法。虽然在复合字体效果的示例页和图像注释中涵盖了很多文本绘制的内容,但本页涉及 “-draw” 操作符的其他更普遍的方面。

绘制命令最初是作为一种创建简单图像的手段。但随着时间的推移,它已经扩展成为矢量图形到光栅图像转换的界面。

ImageMagick 绘制命令 #

计算机中的图像通常以两种不同的方式保存。第一种也是你在这些示例页面中看到的最常见的方式被称为光栅图形。在这种方式中,图像是以像素的矩形阵列来存储的。

另一种方式不太常见,也不太容易修改,但从另一个意义上讲,它的通用性更强,即对象矢量图形。在这种形式下,图像是用线条、弧线、颜色填充,有时还有深度来描述的。这是非常有用的,因为你可以将这些图像放大到你想要的任何尺寸,而且它们仍然可以完美地显示。与光栅格式的图像相比,您还可以在很小的空间内描述非常大和复杂的图像。

矢量图形图像的例子包括 postscript 和新的 SVG-可缩放矢量图形

True-Type 字体也是矢量图形的例子,因为它允许在任何比例下使用单个字符描述。

-draw” 图像操作符,是进入 ImageMagick 矢量绘图功能的一个窗口,并形成了一套与 IM 的普通命令行图像操作符相当独立的命令。

一般使用的矢量图形文件格式只有几种,因为每一种这样的格式通常与其他这样的格式有很大的不同。其结果是,很少有代码共享的可能。 基于这个原因,ImageMagick 更关注使用矢量图形来绘制 SVG 格式的图像。Postscript 和 true-type 字体图形被传递给其他外部的"代理“库和应用程序,它们更适合绘制这些类型的矢量图形格式。 这并不是说 SVG 没有代理库。一个例子是 RSVG 库或 GTK SVG 库,这些库在编译时是可用的。IM 会链接到这些库来转换 SVG,而不是自己尝试去做。

原始绘图命令 #

让我们从 MVG 命令的 “-draw” 图像操作符中最古老、最简单、最常见的绘图原语开始。

请注意,所有的参数都被视为浮点数,不一定是整数,比如我在这些例子中通常使用的。

  # Single Pixel Draw  (两种方式 -- 这些像素点都被放大了)

  # Point 'paints' the color pixel
  convert -size 10x6 xc:skyblue  -fill black \
          -draw 'point 3,2'         -scale 100x60   draw_point.gif

  # Color Point 'replaces' the color pixel
  convert -size 10x6 xc:skyblue  -fill black \
          -draw 'color 6,3 point'   -scale 100x60   draw_color_point.gif

img img

根据给出的注释,当涉及半透明颜色时,这两种点方法会产生不同的结果。详情请参见下面的颜色填充原语

  # 矩形  /  圆角矩形  /  矩形圆弧

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "rectangle 20,10 80,50"       draw_rect.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "roundrectangle 20,10 80,50 20,15"  draw_rrect.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "arc  20,10 80,50  0,360"     draw_arc.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "arc  20,10 80,50 45,270"     draw_arc_partial.gif

img img img img

arc 绘制原语与矩形一起列出,因为它实际上只是一个"椭圆”,装在两个坐标定义的"矩形"(rectangle)内。部分弧线很少使用,因为很难确定端点,除非角度限制在九十度的倍数。

circleellipse 原语涉及"中心"坐标与"边缘"坐标,或"大小"和"角度"值。

  # 圆  /  椭圆    (以某一点为中心)

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "circle 50,30 40,10"          draw_circle.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "ellipse 50,30 40,20 0,360"   draw_ellipse.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "ellipse 50,30 40,20 45,270"   draw_ellipse_partial.gif

img img img

你也可以看看 Push/Pop 上下文,了解如何创建一个旋转的椭圆的例子。

  # 直线 / 折线 / 多边形 / 贝塞尔曲线

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "line   20,50 90,10"                 draw_line.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "polyline 40,10 20,50 90,10 70,40"   draw_polyline.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "polygon  40,10 20,50 90,10 70,40"   draw_polygon.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "bezier   40,10 20,50 90,10 70,40"   draw_bezier.gif

比较好的画直线和曲线的方法是使用 SVG 路径画法,它的用途更广,甚至可以实现"比例画线"。

  # text drawing  / image

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -font Candice -pointsize 40 -gravity center \
          -draw "text 0,0 'Hello'"   draw_text.gif

  convert -size 100x60 xc:skyblue -gravity center \
          -draw "image over 0,0 0,0 'terminal.gif'"   draw_image.gif

最后这两个填充类型的操作是目前唯一受 “-gravity” 影响的绘制操作。这些操作的其他修饰符,包括 “-fill"、”-tile"、"-origin"、"-stroke"、"-strokeidth"、"-font"、"-pointsize"、"-box"。他们还有其他的修饰符,但这些修饰符与更高级的 Magick Vector Graphics 语言有关。

贝赛尔原语 #

“bezier” 原语用于绘制曲线。每条命令只画一条曲线段。通常会给出 4 个点(8 个数字):一个起点’结’、两个控制点和一个终点’结’。两个控制点定义了曲线的方向以及曲线偏离附加的终点’结’点的速度。

为了顺利地将两条曲线连接起来,应该将端点的控制点通过’结’镜像,形成下一条贝塞尔曲线的控制点。例如这里我画了两条平稳连接在一起的贝赛尔曲线。请注意控制线和点(也是画出来的)是如何通过连接坐标直线镜像的,无论是角度还是长度。这一点很重要,否则曲线将不平滑。

  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 "'"`

  convert -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

img

如果我移动其中一个控制点,使它不从同一"结"的另一个控制点通过附加的"结"进行"反射",那么曲线将不连续。

  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 "'"`

  convert -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

img

如果再次移动控制点,使其与相关的"结"点相匹配,线条将直接从该点出发,完全没有任何"曲线"。

  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 "'"`

  convert -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

img

如果两个控制点都设置为各自的"结点",那么就会生成一条直线。

  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 "'"`

  convert -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

img

如果不指定所有 4 个点,‘bezier’ 原语并不真正有用。只有第一个点和最后一个点被归类为’结’,曲线将通过(或结束)这两个点。所有其他的点纯粹被视为控制点,按照给定的顺序对曲线产生影响,控制点越远,对该段曲线的影响越大。

  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`

  convert -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

img

为了保持简单,不建议你每条 ‘bezier’ 曲线段使用超过或少于4个点。

其实我建议你完全不要使用 ‘bezier’ 原语,而是使用 SVG Path Cubic Bezier 来生成曲线。它有一个特殊的 “s” 曲线延续功能,可以自动做相应的控制点"反射",生成平滑连接的曲线段,减少你需要使用的控制点数量。您还可以定义相对于路径中最后一个端点的点。

颜色填充原语 #

除了上述 “简单"原语之外,”-draw“还提供了一组颜色填充或修改原语。这些原语根据所选方法,从指定的点开始修改图像中的颜色。

这些填充方法实际上不是真正的 ‘draw’ 命令,而是颜色替换函数。它们被添加到绘图中,因为在程序的早期版本中,将它们的操作插入到 ImageMagick 中是最简单的。

就像上面一样,使用的颜色是用 “-fill“颜色设置的,但如果设置了,就会使用 “-tile” 图像来代替。

上面的其他设置选项没有使用,对这些操作没有影响。

两个额外的设置也将应用于这些原语,即 “-bordercolor” 和 “-fuzz“系数设置。但是,这些设置不能在 “MVG” 语言中定义,因此只能在使用 “-draw” 操作符之前进行设置。

第一个 ‘color point’ 你已经看到了,它是上述例子中 ‘point’ 绘制原语的替代。如果你仔细观察,你会看到我们在测试图像中设置的单个白色像素。

  convert color_test.png   -fill white \
          -draw 'color 30,20 point'      color_point.png

img

然而在绘制透明色和半透明色时,这些功能是不一样的。

这里我们有一个三个像素的红色图像(放大了),第二个或中间的像素我们用 point 函数在红色像素上画上半透明的蓝色,得到紫色的结果。然而如果使用 color point 函数(最后一个或右边的像素),红色完全被半透明的蓝色像素所取代。它不会被叠加。

  convert -size 3x1 xc:red -matte -fill '#00F8' \
          -draw 'point 1,0' \
          -draw 'color 2,0 point'   -scale 33x33  draw_points.png

img

所有的 color 函数都会进行全色替换,而其他所有的颜色原语都会在图像上面’画’出颜色。因此,你可以使用 color 来绘制透明色。

color replace 绘制函数将在指定的位置替换所有精确给定颜色的实例。而且正如您所看到的,这些区域不一定要连接在一起。

  convert color_test.png   -fill white \
          -draw 'color 30,20 replace'      color_replace.png

  convert color_test.png   -fill white   -fuzz 13%\
          -draw 'color 30,20 replace'      color_replace_fuzz.png

img

然而,正如你在第一个结果中所看到的,一些沿边缘的像素没有被替换。这些像素与所选像素的颜色不完全相同,所以它们被忽略了。添加一个小的模糊因子也会包含与原色相似的颜色。如上面第二个例子所示。

当然,fuzz factor 并不是一个很好的解决方案,因为它不会捕捉所有这样的边缘像素。这是所有这些 color fill 方法经常出现的问题,也是一个没有通用解决方案的问题。

如果你想替换一个特定的已知颜色,而不是从图像本身选择一种颜色,那么可以使用 “-opaque” 图像操作符来代替。该函数还使用 “-fuzz” 因子设置来增加与给定颜色相匹配的颜色范围。

floodfill 的方法也很简单,因为它只会填充所选点周围的整个区域,而不会选择任何其他没有以某种方式连接的类似颜色的区域。

你也可以通过使用 “-fuzz” 来扩大被填充的区域,以包括相似的颜色。在这种情况下,我们选择了一个足够高的值,也包括了交叉边界,允许洪水填充"泄漏"到图像的另一侧。

  convert color_test.png   -fill white \
          -draw 'color 30,20 floodfill'      color_floodfill.png

  convert color_test.png   -fill white   -fuzz 15%   \
          -draw 'color 30,20 floodfill'      color_floodfill_fuzz.png

img img

用颜色填充区域并非没有问题。颜色可能会越过薄薄的边界,渗入到不想要的区域,(请看背景图案上的 GIF,以证明这一点)。或者,它可能无法填满所选区域的边缘,(见反锯齿和泛滥填充问题)。但它确实有效。

filltoborder 就像 floodfill 一样,只是你指定了一个颜色,这个颜色的边界是要填充的区域,而不是填充过程中要替换的颜色。

当然也建议在该边框颜色选择中加入 similar colors模糊因子,以进一步限制洪水填充。

  convert color_test.png   -fill white  -bordercolor royalblue \
          -draw 'color 30,20 filltoborder'   color_filltoborder.png

  convert color_test.png   -fill white  -bordercolor blue \
          -draw 'color 30,20 filltoborder'   color_filltoborder2.png

  convert color_test.png   -fill white  -bordercolor blue  -fuzz 30% \
          -draw 'color 30,20 filltoborder'   color_filltoborder_fuzz.png

img img img

最后的绘制颜色方法是 reset,它只是将整个图像替换或重置为填充颜色。在这种情况下,实际选择的像素对结果没有任何影响。

  convert color_test.png   -fill white \
          -draw 'color 30,20 reset'      color_reset.png

img

这实际上是非常有用的,因为它提供了一种简单的方法从现有图像生成纯色(或平铺图像)画布。(请参见 Canvases Sized to an Existing Image)以了解此方法和其他做同样事情的方法。

未来:使用 “-tile” 图案来填充该区域。

Matt 填充原语 #

matte 绘制原语的工作方式与上述 color 原语完全相同,只是它不会替换所选区域的颜色,只会替换所选区域的 matte 通道。(也就是只有 alphamatte 通道被这些填充函数调整)。

就像 color 填充函数一样,matte 值使用的是填充色(除非用 “-tile” 作为 alpha value 的来源)。

这里我们使用上面同样的 color floodfill 例子,但这里只调整 matte 通道,使填充部分完全透明。也就是说,原来的颜色仍然存在,只是透明而已!

  convert color_test.png   -fill none \
          -draw 'matte 30,20 floodfill'      matte_floodfill.png

  convert color_test.png   -fill none   -fuzz 15%   \
          -draw 'matte 30,20 floodfill'      matte_floodfill_fuzz.png

img img

也可以使用 matte reset 函数使整个图像变成半透明的。当然在这种情况下,我们必须输出到 PNG,它可以接受半透明颜色的图像。

  convert color_test.png   -fill '#00000080' \
          -draw 'matte 30,20 reset'      matte_reset.png

img

注意,在操作中没有使用 black 颜色分量,只使用了颜色的 matte 分量。图像的原色保持原样。

未来:使用 “-tile” 模式来制作有趣的哑光效果。

colormatte 都是完全替换颜色的函数,它总是会产生一个布尔(all or nothing)类型的颜色替换。因此,这些区域的边缘总是会显示出 Aliasing 效果

正因为如此,除了设置 GIF 图像的透明区域(也是布尔型)外,一般来说,这些都不是一般图像显影的好图像运算符。不过也不是全无用处,从背景去除的例子中可以看出。

关于绘图命令的具体内容 #

像素坐标 #

[-draw](https://imagemagick.org/Usage/option_link.cgi?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’。

然而,当您对图像进行数学处理时(如使用扭曲时),实际的像素没有实际意义,因此它使用"图像坐标”。在这个系统中,图像的实际边缘在 ‘0,0’ 和 ‘w,h’ 处。而图像的中心(可能是,也可能不是像素的中心)在 ‘w/2,h/2’。

要将 ‘像素坐标’ 转换为图像坐标,请加上 ½ 如左上角像素的中心是 ‘0.5,0.5’,右下角像素的中心是 ‘w-0.5,h-0.5’。 例如:小图像中的圆心

绘制伽马和色域校正 #

和几乎所有的 ImageMagick 操作一样, [-draw](https://imagemagick.org/Usage/option_link.cgi?draw) 是一个线性运算符,因此在线性 RGB 色彩空间中工作。这意味着,为了得到平滑的边缘,你可能需要对图像进行一些伽玛校正,然后再保存,这样就可以使用非线性(伽玛校正)的sRGB色彩空间来存储。

例如,如果你画了一个大圆,然后保存它…

  convert -size 81x81 xc:black -fill white -draw 'circle 40,40 40,3' \
          circle_raw.png

img

看看圆圈的边缘,其实看起来并不是真的很光滑。你可以看到明显的阶梯效果。

那是因为你是在线性 RGB 色彩空间中画的圆。但是你却把图像保存成了真正的 sRGB 色域!这就导致了你的图像在保存的过程中出现了明显的阶梯效果。

为了解决这个问题,我们需要在保存图像之前给图像添加一个伽玛校正。

  convert -size 81x81 xc:black -fill white -draw 'circle 40,40 40,3' \
          -gamma 2.2 circle_gamma.png

img

现在,圆圈边缘实际上看起来光滑圆润,就像它们应该的那样。

如果你想正确地做这件事,我们真的应该使用色彩空间进行修正。然而,由于IM假设RGB是保存的默认色彩空间,你需要做一些棘手的处理来让它正确地做事情。

  convert -size 81x81 xc:black -set colorspace RGB \
          -fill white -draw 'circle 40,40 40,3' \
          -colorspace sRGB circle_sRGB.png

img

请注意,sRGB色彩空间(这是保存图像的正确方法)与简单地应用2.2伽玛校正并不完全相同。然而,两者之间的结果差异很小,只有在非常非常粗糙的图像中才能看到。 在IM v6.7.5-1之前,色彩空间名称 “sRGB” 和 “RGB”(线性-RGB)实际上是颠倒的。因此,在旧版本的IM中,上面的两个标签应该被调换。

要使用真实的图像(在IMv6中)正确地绘制(或进行任何"线性"图像处理),你需要首先删除任何现有的伽玛,处理图像,然后恢复该伽玛校正。更多细节请参见使用色域校正调整大小

下面是一个在真实图像上绘图的例子…。首先是没有任何颜色校正(原始),然后是伽玛和色域校正。

  convert rose:  -fill none -stroke white -draw 'line 5,40 65,5'  rose_raw.png

  convert rose: -gamma .454545 \
          -fill none -stroke white -draw 'line 5,40 65,5' \
          -gamma 2.2 rose_gamma.png

  convert rose: -colorspace RGB \
          -fill none -stroke white -draw 'line 5,40 65,5' \
          -colorspace sRGB rose_sRGB.png

img img img

正如你所看到的,通过使用伽玛或色彩空间校正,线条变得非常平滑,没有锯齿状的"楼梯"别离效果,比直接绘制时可以看到。(你需要一个很好的显示器才能看到它)

上面的线条是用 [-stroke](https://imagemagick.org/Usage/option_link.cgi?stroke) 颜色绘制的。您可以使用 [-fill](https://imagemagick.org/Usage/option_link.cgi?fill) 来绘制线条,并得到同样的结果,但这样您就不能使用 [-strokewidth](https://imagemagick.org/Usage/option_link.cgi?strokewidth) 来控制线条粗细。更多信息请参见下面的”描边颜色设置"。 色域名称实际上是使用’sRGB’色彩空间的值来定义的,但通过绘制应用,就好像图像是在线性RGB色彩空间中一样。因此,对命名的颜色(不是"白"或"黑”)使用上述伽玛校正将导致这些颜色变得失真。在这种情况下,最好不要使用伽玛或色域校正,这样命名的颜色就会正确映射。 命名的’sRGB’颜色与图像的色彩空间的正确映射,将作为IMv7开发的一部分被修正。

描边、描边宽度和填充的相互作用 #

[-stroke](https://imagemagick.org/Usage/option_link.cgi?stroke)[-strokewidth](https://imagemagick.org/Usage/option_link.cgi?strokewidth) 选项用于在字体边缘绘制轮廓。

这些选项通常与 [-fill](https://imagemagick.org/Usage/option_link.cgi?fill) 一起使用,以使文字更有趣,而不费力。

    convert -size 380x70 xc:lightblue -pointsize 50 -font Chisel \
            -fill green  -stroke black  -draw 'text 10,55 "Black Border"' \
            stroke_font.jpg

默认设置是 -strokewidth 1-stroke None

但这样做会使轮廓笔画不可见,只留下 [-fill](https://imagemagick.org/Usage/option_link.cgi?fill) 的颜色,你不会看到它。

-strokewidth 为 “不可见 “时, -strokewidth 唯一的效果是对字体大小属性的影响,也就是说它仍然可以影响字体定位和 Label 和 Caption 图片生成的大小。否则,宽度在你使笔画可见之前是没有可见效果的。

为了了解 [-strokewidth](https://imagemagick.org/Usage/option_link.cgi?strokewidth) 对字体外观的实际影响(当使其可见时),我在这里绘制了一些不同宽度的文字,从 “turned off” 到越来越大。

    convert -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

img

请注意,从上面的例子中,设置 -strokewidth 为 “0” 与设置 -stroke 颜色为 “none”(默认值)是不同的。前者会画出一个非常非常细的笔触轮廓,而后者则会有效地关闭它。在这两种情况下,笔触仍然会被绘制。

然而你也应该注意到,即使 -strokewidth 为 “0”,图像的轮廓也会比普通的 filled 图像(使用 -stroke 颜色为 “none”)扩大非常小。

最后,使用任何小于 “1.0” 的宽度都不能正常工作。在这种情况下,你应该谨慎行事。

但是请记住, -strokewidth 也是一个浮点设置。也就是说,“0.5” 的笔画宽度也是有效的。然而,通常只有当您试图在关闭防锐化功能的情况下绘制薄的位图圆时,这才是重要的。

下面是一个使用超大笔触宽度的例子。

   convert -size 320x100 xc:lightblue -font Candice -pointsize 72 -fill white \
           -stroke black -strokewidth 15 -draw "text 25,65 'Anthony'" \
           stroke_thick.jpg

img

请注意, -strokewidth 可以向内和向外扩展线条。下面是同样的例子,但是重新绘制了字体,没有笔画轮廓,去掉了很粗的笔画的内侧部分。

   convert -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

img

更多使用描边的例子请看复合字体效果。请特别看一下”气球效果"。

绘制(描边)线条 #

IM中的默认画线有一些奇怪的行为,值得了解。下面就为大家介绍一下默认画线…

  convert -size 100x40 xc:lightblue \
          -draw "line 5,35 95,5" \
          line_default.jpg

img

你可以用 -fill 选项设置线条的颜色。

  convert -size 100x40 xc:lightblue \
          -fill white -draw "line 5,35 95,5" \
          line.jpg

img

此外,你还可以通过设置 [-stroke](https://imagemagick.org/Usage/option_link.cgi?stroke) 颜色,使线条稍微粗一些。

  convert -size 100x40 xc:lightblue \
          -fill white -stroke black -draw "line 5,35 95,5" \
          line_stroke.jpg

img

但是我们用 -fill 选项指定的白色是怎么回事呢?

这就是在 ImageMagick 中绘制线条的棘手之处。该程序所做的实际上是将线条视为一个约1像素宽的填充对象。这是自然的,因为通常情况下,多条线通常用于扫出一个要填充的区域。

所以,就像我们在上一节中使用字体的描边一样,IM 用填充色绘制线条(或对象),然后用描边色在其周围绘制。结果就是,现在上面的描边色线条稍微粗了一点,填充色完全隐藏在下面。如果你把描边色做成半透明的,就可以让这个填充色再次显现出来。

综上所述,线条会出现在 -fill 颜色下绘制,但一旦 -stroke 颜色被定义为默认的"无"或"透明"颜色以外的颜色,该选项就没有任何意义了。

选项 -linewidth 实际上只是 -strokewidth 的别名,不应该被使用。

例如,你可能会认为这个命令会产生很粗的线条。确实如此,但由于 -stroke 的颜色是不可见的,所以你看不到它。你只能看到线条的一个像素宽的区域的内部"填充”。

  convert -size 100x40 xc:lightblue \
          -fill white -strokewidth 3 -draw "line 5,35 95,5" \
          line_fill_3.jpg

img

以上的结果其实我认为是一个 BUG,什么都没画,因为没有’区域’填充,也没有设置线的’描边颜色’。什么都没有画,因为没有"区域 “需要填充,也没有设置线的"笔触颜色”。IM 目前这样做的原因是为了避免新用户的混淆,但实际上这只会给高级用户带来问题。详见《绘制填充边界》

但如果同时定义了描边颜色,就会得到要求的粗线…

  convert -size 100x40 xc:lightblue \
          -stroke black -strokewidth 3 -draw "line 5,35 95,5" \
          line_stroke_3.jpg

img

如果将 -strokewidth 设置为1,则上面的一行将被完全覆盖。

  convert -size 100x40 xc:lightblue \
          -stroke black -strokewidth 1 -draw "line 5,35 95,5" \
          line_stroke_1.jpg

img

当然当你掌握了这些知识后,你就可以利用这些知识进行创作,就像画字体一样。

  convert -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

img

在这里,我使用了最薄的 -strokewidth 设置为 “0”,就像我对上述字体所做的一样。

  convert -size 100x40 xc:lightblue \
          -fill white -stroke black -strokewidth 0 -draw "line 5,35 95,5" \
          line_stroke_0.jpg

这就产生了一个非常奇怪的结果,即由黑点和灰段组成的点线。这是笔触、填充和背景色之间奇怪的"色拍频率"的结果。

下面是线条的放大图…

  convert -size 25x10 xc:lightblue \
          -fill white -stroke black -strokewidth 0 -draw "line 2,8 22,1" \
          -scale 400%    line_stroke_0_white.jpg

img

颜色节拍频率 “的效果与"声音节拍"的效果并无二致,当你有两把非常轻微的不调的吉他时,你会得到这样的效果。在这种情况下,你会得到一个黑点,其中笔触颜色完全覆盖了基本的填充颜色,你会得到一个灰色的点,其中笔触颜色与填充和背景颜色混合。 颜色混合是反锯齿过程的自然结果,IM使用反锯齿过程来尝试改善线条和其他绘制对象的外观。更多信息请参见 IM 中的抗锯齿讨论和示例页面。

需要注意的是,这种效果只出现在倾斜的线条上,而不是纯水平或垂直的线条,因为在这些线条上,别名没有影响,因此也就没有"色拍频率"的效果。

  convert -size 100x40 xc:lightblue \
          -fill white -stroke black -strokewidth 0 -draw "line 5,20 95,20" \
          line_stroke_horz.jpg

img

在这里,我在放大的视图上使用了不同的底层填充颜色,所以你可以看到颜色如何改变结果的节拍。

  convert -size 25x10 xc:lightblue \
          -fill none -stroke black -strokewidth 0 -draw "line 2,8 22,1" \
          -scale 400%     line_stroke_0_none.jpg

  convert -size 25x10 xc:lightblue \
          -fill red -stroke black -strokewidth 0 -draw "line 2,8 22,1" \
          -scale 400%    line_stroke_0_red.jpg

  convert -size 25x10 xc:lightblue \
          -fill black -stroke black -strokewidth 0 -draw "line 2,8 22,1" \
          -scale 400%    line_stroke_0_black.jpg

img img img

让我们把它和无 stroke 相比…

  convert -size 25x10 xc:lightblue \
          -fill black -stroke none -draw "line 2,8 22,1" \
          -scale 400%    line_stroke_-_black.jpg

img

正如你所看到的,当绘制非常细的线条时,你可以通过使用相同的填充和描边颜色,或者将其中一种颜色设置为无来减少"节拍”。虽然后者是最好的主意,但前者可能对你的特定编程需求更实用。

注意,填充线的粗细是 “0”。但描边线可以有更大的厚度。它也是一个浮点值! 2.5像素宽的线条是完全有效的。

这些结果不仅是由于笔画宽度为0的bug,导致颜色跳动,而且在没有实际需要填充的区域时,“填充色"被画出了额外的1.0直径厚度。这我也认为是一个bug。请看绘制填充边界

绘制填充边界 #

关于各种绘制原语,还有一些其他要点需要您注意。

笔画宽度对于大于 1.0 的浮点值工作得很好,但对于小于 1.0 的值似乎会崩溃。这是由于使用的实现算法造成的,而不仅仅是因为它是错误的,因为它在较大厚度的线条上工作得很好。

基本上,如果你使用的笔画宽度为零,你可以期待没有笔画颜色会被添加。相反,你会得到一种节拍模式,当线条穿过像素的实际"中心"时,笔触颜色就会达到全部强度。

真正应该发生的是,添加到像素上的颜色数量应该反映出被绘制的线条的面积,而不是像素与该线条的距离。因此,零宽度的线条不应该给图像添加任何颜色,而厚度小于1.0的线条应该只添加较少的颜色。

请看上面的例子 Draw Lines, with StrokeWidth and Stroke

另一个问题是,填充颜色没有应用到正在绘制的形状(多边形)的边缘,而是应用到更远的半像素处。这包括没有应用"描边"的情况,而边缘应该是精确的。它也包括画一条"线”,它的填充厚度实际上是"零”。

基本上,如果你画了一条线,没有启用描边,从技术上讲,你应该看到,没有线,因为它没有"填充"厚度。相反,线条的绘制至少包括1个像素宽的"填充"颜色。这是出于历史原因,一般来说可以避免新用户对IM的混淆。不幸的是,这对高级用户来说是不正确的。

这意味着,如果您只使用填充色绘制两个多边形,并共享一个边缘,该边缘将重叠1个像素,因为每个多边形的所有边缘都比它大半像素。换句话说,多边形和其他形状并不适合在一起,而是重叠的。

例如,在这里我试着使用 draw 把一张图片分成两半(在白色上绘制黑色)。要做到这一点,我画了两个多边形,共享一个边缘,完全没有重叠。由此产生的"微小"图像,已被放大显示。

  convert -size 10x10 xc: -draw 'polygon 2,-1 7,10 10,10 10,-1' bound_left.gif
  convert -size 10x10 xc: -draw 'polygon 2,-1 7,10 -1,10 -1,-1' bound_right.gif
  convert bound_left.gif bound_right.gif -compose Plus -composite bound_add.gif

img img img img img

两个黑色的部分(这是实际绘制的)实际上是相互重叠的! 换句话说,尽管我们试图使用绘制的多边形分别绘制这两个区域,但填充的区域比要求的略大。

我还将两张图片加(加合成)在一起,这样你就可以实际看到绘制的黑色区域的重叠。如果两个多边形是完美契合的,那么 “添加 “的图画将是纯白色的。

实际的重叠量相当于默认的 -strokewidth 1.0 设置。因此,通常情况下,这个额外的区域会被一个正常的笔画宽度所覆盖。然而它可能会造成一些实际问题。

旁白: 对于一个完整的连接测试,你会在黑色背景上生成50%的灰色区域,然后把它们加在一起。这样你就可以看到这些区域是否不仅 “重叠”(如上图所示),而且还可以测试当你把这些区域加在一起时,它们是否 “重叠不足”(在填充的区域之间留下一个间隙)。所得到的图像应该是一个完美平滑的50%灰色,沿连接处没有颜色变化。透明度检查会涉及到,在一个完全透明的背景上应该使用50%透明,50%灰色的颜色。

要查看一个完美的剪切和重新添加的例子,基于一个单一的蒙版图像,请参阅组成方法的例子,组成 DstOut

未来BUG修复:填充的区域应该是精确的,但为了在绘制形状时进行补偿,默认的 “描边颜色 “应该设置为填充颜色(除非它本身被特别设置)。

MVG - 魔法矢量图形 #

上面显示的原语构成了所有 -draw 操作的基础。它们共同构成了 ImageMagick 中特殊内部语言的起点,称为 Magick Vector Graphics 语言。关于这种语言的更多细节,请参见IM网站上的 MVG 原语和语法摘要

这种 “MVG” 语言的设计目标是让 ImageMagick 处理更复杂的 SVG(可缩放矢量图形)语言。它通过尝试将给定 SVG 格式的图像转换为更简单的内部 MVG 格式来实现。更多细节请看下面的 SVG 处理

因此,你上面看到的只是 -draw 操作符的一小部分功能。如果你想绘制复杂的对象,我建议你使用SVG编辑器(如 “Sodipodi”)为对象创建一个单独的SVG格式图像。参见下面的非IM矢量图形程序)。

与 SVG 不同,MVG 没有任何形式的"容器"或图像命令集。在转换过程中,这些命令都被删除,以产生一个简化的 MVG 绘图命令序列。相反,它使用图形上下文的概念来保存和恢复各种绘图设置,这就是我们现在要看的。

命令行设置与 MVG 设置 #

首先,您通过命令行选项设置的几乎所有设置,绘制原语使用的设置在 MVG 绘制命令中都有直接对应的内容。

通过命令行选项(如 -strokewidth)或使用 MVG 绘图字符串(如 strok-width)中的设置,两者之间的主要区别在于 MVG 设置只在 MVG 命令字符串的持续时间内有效。

一般绘图设置的总结:

  __cmd_option__   __draw_MVG__        __Argument__
    -fill            fill                color/tile for inside shapes
    -tile            fill                image tile, replaces fill color

    -stroke          stroke              line color/tile around the shapes
    -strokewidth     stroke-width        pixel width
    +antialias       stroke-antialias    0/1 aliasing line edges

    -font            font                font_name / font_file
    -family          font-family            ?
    -weight            ?                    ?
    -stretch           ?                    ?
    -pointsize       font-size           height in points
    -kerning           -                 extra inter-character spacing

    +antialias       text-antialias      0/1 aliasing drawing text
    -box             text-undercolor     fill color for font bounding box
      -              decorate        (None, Underline, LineThrough or Overline)

    -gravity         gravity             (None, North, South-East,...)
    -fuzz              -                 color delta / percentage
    -bordercolor       -                 color

Notes:

  - no such option      ? unknown

这些设置通常很好理解,因为它们经常使用,上面也有演示。

字体、拉伸、样式和重量用于从 ImageMagick 字体列表中识别字体。然而,大多数人只是选择一个特定的字体和大小点来代替使用。因此,它们在IM中很少使用。 正如您所看到的,“color fill” 原语的特殊设置在 MVG 中并没有直接对应的设置。这就是 -bordercolor-fuzz 因子设置。在使用 -draw 操作符之前,必须从命令行指定这些设置。

有些 MVG 设置作为全局命令行设置可能更有用,比如字体绘制的 decorate 设置。

警告: [-gravity](https://imagemagick.org/Usage/option_link.cgi?gravity) 不是 SVG 规范的一部分。在 MVG 中,它只用于文本和图像的放置以及对齐。目前没有与默认的"引力"效果分开的调整设置。然而,由于调整是 SVG 文本处理的一部分,这可能会在未来的某个时候改变。

现在,全局命令行设置(在 MVG 绘制字符串之外)用于初始化你所应用的每个 -draw 操作的设置,这就是为什么你可以设置一个 -fill 颜色,然后你可以用它来绘制该颜色的圆。

  convert -size 100x60 xc:skyblue   -fill red \
          -draw "circle 50,30 40,10"          draw_circle_global.gif

img

你可以在 MVG 参数 -draw 中本地覆盖全局设置…

  convert -size 100x60 xc:skyblue   -fill red \
          -draw "fill green   circle 50,30 40,10"  draw_circle_override.gif

img

然而,在单个 -draw MVG 参数中设置的设置只在 -draw 操作期间存在。也就是说, -draw 中的设置只限于该次绘制,而不会带入以后单独的 -draw 参数中。

  convert -size 100x60 xc:skyblue   -fill red   -draw 'fill green' \
          -draw "circle 50,30 40,10"          draw_circle_local.gif

img

如果你打算进行大量的操作,那么在单个 MVG 字符串中进行这些操作可能会比多个 -draw 操作更好。

  convert -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

img

MVG 特定设置 #

其他控制线条和对象绘制方式的 MVG 设置,即使在使用原语操作时也需要了解。这些设置包括…

   __draw_MVG__       __Description/Argument__
  fill-opacity        fill transparency, from 0.0 to 1.0
  clip-rule           fill style for crossed lines (evenodd, nonzero)

  stroke-opacity      line transparency, number from 0.0 to 1.0
  stroke-dasharray    list of 'on' and 'off' lengths for lines
  stroke-dash
  stroke-linecap      End of line look: butt round square
  stroke-linejoin     Lines joins:  butt  miter round square
  stroke-miterlimit   Angle when 'miter' joins become 'bevel' (or 'butt')

记住,所有 MVG 设置和绘图操作符的完整列表可以在 IM 网站的 MVG 原语和语法摘要中看到。

让我们看看一些简单设置的效果……

  # Stroke Opacity
  convert -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
  convert -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
  convert -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

  convert -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
  convert -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

  convert -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

  convert -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

img img img img img img img

stroke-miterlimit 的设置是相当难以演示的。这个属性定义了将 miter 连接变为 bevel 连接的角度。基本上对于非常尖锐的角度,一个斜面可以从两条线的实际接合处延伸很长的距离。这就为这个锐角设置了一个最大限度,当它变得太长时,就会使角点变钝。但请注意,它代表的是某种角度的三角值,而不是长度或距离。该值必须大于1.0。

上面显示了对于我所显示的连接角度,斜角会突然转换成一个介于6到7之间的斜角。

例如,1.414 的 troke-miterlimit 会将小于90度的任何角度的 “miter” 转换为 bevel 。4.0的 值(默认值)将小于约29度的角度转换为连接。而10.0的值可以将小于约11.5度的角度转换为斜面。

SVG s路径绘制 #

SVG 路径是 SVG 的基本绘图原语。它用于绘制线型、圆、曲线、弧线等。SVG 路径的完整规范可以在 SVG 路径规范文档中找到。

然而这并不是一个容易阅读的文档,因为它确实是为程序员而不是用户准备的,所以我将简化和总结路径规范…

  • 字母是命令,而所有的数字(浮点)都是参数
  • 逗号或空格可以作为参数分隔符,否则完全忽略
  • 每个路径组件的最后两个参数 (x,y) 将成为该路径组件的终点(或"结”)
  • 大写字母是指最终点的绝对坐标
  • 小写字母是相对于前一个组件的终点而言的
  • 例如 “M 1,2 L 3,4 L 2,4” 和 “M 1,2 L 4,6 L 6,2” 是一样的。
  • 即在1,2上加了3,4,画线到4,6。
  • 然后在1,2上加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 " 相同。
  • 对于立方贝塞尔,所有的点(控制点和结点)都是相对于前一个路径组件的端点而言的。

请注意,您可以用绝对坐标或相对坐标来指定对象。因此,你可以用相对坐标来定义一个对象,只需提供一个初始的绝对"移动"坐标来定位整个路径。

另一方面,你也可以使用其他的"图形内容"命令来移动整个图形在"视图框"或"转换"中的位置(见下文)。因此,在 SVG 路径中使用绝对或相对坐标并不重要。 移动、线条和路径闭合是学习 SVG 对象路径的最初起点。

  # Open, Completed and Closed Paths (same points)

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "path 'M 40,10 L 20,50 90,10 70,40'" path_open.gif

  convert -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

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "path 'M 40,10 20,50 90,10 70,40 Z'" path_closed.gif

img img img

但是请注意,‘z’ 只是关闭循环。它并没有创建一个单独的对象。因此,两个"关闭"的路径仍然被归类为一个单一的绘制对象,无论它们是重叠的还是完全断开的。

这里我们展示了两个闭合但重叠的循环,在同一方向上绘制。由于只使用了一条路径,所以对象是一个单一的对象,fill-rule 设置控制了重叠区域的填充方式。

  # Overlapping Paths and Fill Rule
  convert -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

  convert -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

img img

由于对象围绕中心以相同的角度方向绘制,两个闭合的循环将包含一个周期值为2的区域,因此 evenodd 规则使得该区域未被填充,而非零的 nonzero 规则则将其填充。但是请注意,所有的路径都是可见的,因为它们实际上是同一个对象。

绘制路径的方向是非常重要的,一般情况下,所有的路径相对于对象的’内部’应该绘制在完全相同的方向上。

例如这里我将第二个对象画成与第一个对象相反的方向。因此,当两个对象重叠时,该区域被圈出 ‘0’ 次。也就是说,无论使用什么 fill-rule,它都将是未被填充的,形成一个’洞’。

  # Overlapping Closed Objects, Second object drawn in reverse
  convert -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

  convert -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

img img

这意味着你可以在物体上产生一个"洞”,通过反转方向,使物体的"内部"保持在行进方向的同一侧。

  # An object with a reversed drawn hole!
  convert -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

img

无论 fill-rule 的设置如何,结果都是一样的,因为这个洞是’偶数’和’零’,所以是未填充的。

当然,如果您使用一个完全独立的 path 元素,您将生成一个完全独立的对象。在这种情况下,fill-rule 不适用,而只是按照给定的顺序,将对象画在彼此的上方。

  # Separate paths are separate objects
  convert -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

img

未来:坐标对准路径 “H” 和 “V”。

椭圆弧是 SVG 路径的圆圈绘制功能…

largesweep 参数特别重要,因为它们用于决定从起点到终点的四种弧线中的哪一种。

largesweep 这两个标志定义了四条弧线中哪一条弧线将连接这两个点。

  #  Elliptical Arcs :   A  radius_x,y  angle   large,sweep  x,y
  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "path 'M 30,40  A 30,15 0 0,0 70,20'"    path_arc.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "path 'M 30,40  A 30,15 0 0,1 70,20'"    path_arc2.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "path 'M 30,40  A 30,15 0 1,0 70,20'"    path_arc3.gif

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "path 'M 30,40  A 30,15 0 1,1 70,20'"    path_arc4.gif

img img img img

第二个标志 sweep 简单地决定了弧线路径的方向的哪一边应该被绘制。

large 标志是用来选择较长的路径,绕着椭圆的中心走。这是设置的角度的弧线将大于180度。如果关闭,你会得到较小的’弧’,不包含椭圆的中心,并且弧线的角度小于180度。

用 “Z” 来关闭弧线,只是画出最后的直线段。

要创建一个完整的椭圆或圆,你至少需要两个’弧线’段,从第一点到第二点,然后回到第一点。两条弧线都应该有相同的 sweep 设置,所以弧线将在不同的边上,有不同的移动方向。其中一条弧线应该有 large 的设置。

  # Closed and angled elliptical arcs  (defined by two edge points)

  convert -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

  convert -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

  convert -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

img img img

请注意,如果直线太长,在给定的角度下无法适应给定的椭圆大小,椭圆的大小将被放大,以适应以椭圆为中心的直线,这意味着通过使用小数字作为轴半径,你可以只指定轴长的比例,并保证直线路径通过椭圆的中心点。

这意味着,通过使用小数字的轴半径,你可以只指定一个轴长的比例,并保证直线路径穿过椭圆的中心点。也就是说,路径从椭圆的一侧到另一侧形成一个椭圆直径。这并不是椭圆的主轴或次轴,只是一个椭圆的直径。

  convert -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

img

当然,使用长度为 “1,1” 的结果是一个完美的半圆,从一个点,到下一个点。在这种情况下,椭圆角不会有任何区别。

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "path 'M 30,40   A 1,1  0  0,0 70,20'" path_hcircle.gif

img

对于以两点为中心的全圆,用…

  convert -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

img

SVG 对 “弧线"的定义也声明,如果两个半径中的任何一个是0,那么就应该画一条直线。因此,任何半径为 “0,0” 的圆弧,都只是一条简单的直线圆弧…

  convert -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

img

如果你为弧线指定了一个非常大的半径,而没有为回程路径指定 large sweep,你可以在两点之间创建该半径的透镜形状。

  convert -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

img

这种类型的弧线是一个关键特征。它可以让你很容易地把原本是直线的东西变成一条小而明显的曲线。

例如,与其说是一个简单的三角形,不如说是一个…

  convert -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

img

你可以用一个大半径的弧线代替每条线,让它们只是有轻微的曲线。

  convert -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

img

线条的端点没有变化,所发生的只是每个 “L” 被一个弧线段代替。然而弧线的大小应该与线的长度成正比。由于我没有这样做,较长的对角线比其他两条线有更深的曲线。

请记住,当调整对象的大小或比例时,你也应该将半径的比例与线的长度相同,这样曲线的大小就会相应地调整,所以弧线的比例也会正确。

请注意,sweep 标志可以控制曲线是向外凸起还是向内凸起,这取决于每个路径段的绘制方向(见上文)。

  convert -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

img

看起来 “静态"的直边三角形,现在看起来有点像充满风的帆。

如果你真的想让线条完美的直,而不把它们转换回真正的线段,你可以通过使用弧线半径为零来关闭曲线。

因此,弧线不仅适合生成椭圆和圆,而且对于绘制直线和微曲线段也很有用。它是一种非常通用的通用点到点的绘制路径。

使用椭圆弧来生成分离的曲线段的一个简单的替代方法是使用四元贝塞尔段来代替,主要的区别是使用一个单一的控制点,而不是一个圆形半径来定义弧线。这也允许您将弧线偏向线段的一端,但代价是难以生成对称的弧线。

当然,您也可以通过使用这两种方法来进行 mix-n-match

饼图示例 #

为了完成对弧线的使用,让我们举个例子,使用为它们生成圆楔。当然,你可能需要使用一些外部的三角数学(你的高中数学有多好?)来确定所需的最终路径点。

  convert -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

img

请注意,所有的弧线都是画在 line path 的左边,并有相应的标志(使用 sweep 标志)。但如果弧线覆盖的角度大于180度,则需要设置 large 标志。见上面例子中最后一个 gold 组件。

还要注意的是,你应该完整地画出每个部分,尽管这意味着你可能要画两次边界线。如果你不这样做,你很可能要么不会完全用颜色填充该部分,要么填充颜色会覆盖之前绘制的部分轮廓。

避免重复绘制多条线的唯一方法是绘制所有填充区域,然后重复这样绘制轮廓。就是说你需要把所有的东西都画两遍,确保东西正确匹配。因此,将轮廓加倍可能是最简单的解决方案。

立方贝塞尔曲线可以使用 c 函数定义两个控制点,以及最终的终点。对于使用最后一个控制点的镜像的持续立方贝塞尔曲线(对于连续曲线),你可以使用 s 函数。

下面是一个例子。由于这个函数的复杂性,我预先准备了一个画布,显示控制点的位置,以及最后一个控制点的 assumed mirror

  # Cubic Bezier:    C  control_1_x,y control_2_x,y  x,y
  # Smooth " :       S  control_2_x,y  x,y

  convert 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

img

连接控制点和该路径段路径上的最终点的线(控制线)基本上定义了通过路径上该点的曲线方向。长的控制线会在该点产生一条更平滑的曲线,而短的控制线则会在该点产生一条更清晰的曲线。如果控制点与曲线的点相吻合(控制线长度为零),则曲线在该点有一个尖锐的不连续性,就像只用直线段一样。

作为一个更实际的例子,下面的代码是从 IM 实例 Logo 生成器脚本中提取出来的,该脚本创建了 IM 实例 Logo 的曲线溅射区域。

这个例子的棘手之处在于,我将我使用的立方贝塞尔路径字符串,转换为另一个路径,显示用于生成贝塞尔曲线的控制线。这让我可以看到曲线的控制线角度和长度,使得调整结果变得更加容易。只需要调整一组点就可以同时显示曲线和控制线,将错误控制在最小范围内。

   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/ /;' -`
   convert -size 100x100 xc:white \
           -draw "stroke None  fill Green  path '$curve'" \
           -draw "stroke Red   fill None   path '$c_ctrls'" \
           curvy_splash.gif

img

如果你仔细观察图像,你会发现曲线的起点和终点有两条方向相反的控制线。对于一个封闭的连续路径来说,开始和结束的控制线应该在相同的角度(只是在镜面方向),当然也应该是相同的长度。这一点很重要,因为很容易弄错。

沿着曲线的所有其他点只有一个控制点/线,它与曲线的绘制方向相反。该线段越长,曲线在该控制点处的 sharp 越低,长度为零则产生一个 point

s 函数在内部根据前一段的数据生成下一段的镜像控制点/线,从而产生曲线的平滑延续。

关于这个路径函数的更多例子,请看《SVG:立方贝塞尔曲线命令》

手动生成贝塞尔曲线是比较直接的,不需要任何花哨的 GUI 工具。

  • 首先定义所有你想让曲线经过的坐标点,在列表的最后重复起始坐标。
  • 现在将这个列表扩大,将所有的 x,y 坐标点加倍成对,并在每对坐标点前添加一个 s(Smooth Cubic)函数。每对中的第一个数字是控制点,连接到第二个数字代表曲线上的点。然而第一个点对却把这一点反过来了,第一个点是曲线的起点,第二个点代表第一个也是唯一一个反转的控制点。
  • 将第一对坐标的函数字母由 ‘S’ 改为 ‘M’,然后在这对坐标之间加一个 ‘C’。最后将第二对坐标的 ‘S’ 去掉,完成初始的立方体(‘C’)函数。
  • 通过添加最后的 ‘Z’ 来完成路径,关闭曲线。
  • 请看上面的示例序列,它应该是怎样的。
  • 此时您可以测试绘制您的路径。由于所有的控制线长度为零,所以路径将只由直线段组成。
  • 现在您需要做的就是慢慢地、小心地调整控制线段的位置(每个 “S” 对的第一个坐标),以得到您想要的最终曲线。不要把控制线做得太长,或者方向不对,否则你会得到一条看起来非常滑稽的曲线。
  • 为了帮助查看你的变化和发现错误,请使用上面的转换 “sed” 命令来绘制路径控制点和曲线控制点之间的控制线。但是请注意,零长度的控制线是不可见的,但是由于lin会产生一个尖锐的点,所以位置应该很明显。
  • 最后,确保 “C” 之后的第一条控制点/线与终点控制点/线的位置完全相反。

交互式曲线的生成也可以通过使用一些矢量图形编辑器来实现。

例如 Luis Guerra 报告说,“Inkscape” 生成的贝塞尔曲线可以使用 “Edit -> XML Editor” 功能,然后选择你想要控制点的路径或形状。

你知道有什么其他的方法可以用GUI工具来提取贝塞尔曲线(在曲线上每个点给出两个或一个控制点)。或者是一些其他的技术来生成这样的曲线?请发邮件给我! 我很乐意听到它。你将会像其他人一样,被记入该技术的名下。

二次方贝塞尔函数是立方贝塞尔函数的简化,当两个控制点合并成一个控制点时。同样,你可以用 ‘Q’ 函数开始曲线,然后用 ‘T’ 函数继续曲线,镜像最后一个控制点。

  #  Quadratic Bezier:  Q  control_x,y  x,y
  #  Smooth " :         T  x,y

  convert path_quad_canvas.gif  -fill white -stroke black \
          -draw "path 'M 10,30   Q 20,4 50,30   T 90,30' " \
          path_quad.gif

img

但我要提醒您,“T” 继续函数只适用于连接间距相等的点的路径。我不推荐使用它。

二次方曲线的优点是可以替代椭圆弧,因为它使用的是实际的位置,而不是弧线的半径。它也可以使弧线偏向于一端而不是另一端,这在使用椭圆弧时并不实用。

  convert -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

img

在这种情况下,弧线不是那么均匀,你得到的东西就像一个倒立的鲨鱼鳍,而不是一个帆。

请记住四边形弧线是抛物线,而椭圆弧线基本上是生成圆弧线段。这可能是决定你应该使用哪种类型的弧线段的关键。

有关此路径功能的更多例子,请参见:SVG: Quadratic Bezier Curve Commands

绘图表面的变形 #

在这些能力之上,绘制对象的绘图表面可以以各种方式变形,让你做一些令人惊奇的事情。

首先,你可以应用一些通用的绘图表面修改,比如… “translate”、“rotate”、“scale”、“skewX”、“skewY” 和 “affine”。

例如,给定一个线条的 path,我们可以 translate 绘图表面的原点或0,0点到另一个位置。

  convert -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

img

请注意,“0,0” 或绘图区域的原点现在以图像为中心,尽管Y轴在图像的顶部仍然是负数,在底部仍然是正数。

rotate 操作将旋转绘图表面,所以以后在该表面上绘制的任何东西都将被旋转绘制。当然,它会围绕转换后的原点进行旋转,所以最好同时使用这两个变换运算符。

  convert -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

img

scale 将放大和缩小原点周围的绘图面。

  convert -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

img

一个常见的 scale 用法是将Y轴翻转,使Y的正值向上。当然原点也应该移到中心,或者左下角,以保持秩序。

  convert -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

img

最后,“skewX” 和 “skewY” 在X和Y方向上对图像进行剪切。例如,这里我们使用 “skewX” 给图像的垂直Y轴一个倾斜。

  convert -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

img

这些操作符在 MVG -draw 字符串之外有对应的操作符,供一般使用。但是这些命令行版本是运算符,并且立即应用于内存中已经存在的图像,而不是应用于尚未绘制的矢量对象的绘制表面。更多细节请看扭曲图像。

绘制表面的平移变形 #

上述所有五种画布变换都可以组合成一个通用的 Affine Matrix Operator,可以使用 MVG 原语 “affine”,也可以在调用 -draw 之前使用 -affine 设置 Affine 变换。

Affine 变换使用一组 “Matrix Coefficients”,它定义了如何将你给出的坐标修改为实际的绘图坐标。

关于这些 “coefficients” 如何工作的更多细节,请参阅 Affine Matrix Transforms

例如… 要设置一个相对于对象绘制时的中心原点…

  convert -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

img

翻转图像…

  convert -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

img

绕原点旋转30度…

  convert -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

img

对于更复杂的仿射变换,您可以使用为此目的创建的仿射辅助脚本。这些脚本将诸如旋转角度和中心点之类的东西转换为 Affine 坐标,你可以直接在 -draw affine-affine 设置中使用。

推/弹上下文 #

一些 MVG 原语实际上依赖于这些变换的使用才能正确使用。例如,椭圆原语只能用正交对齐的轴直接指定。

  convert -size 100x60 xc:skyblue -fill white -stroke black \
          -draw "ellipse 50,30 30,15 0,360"   ellipse_orthogonal.gif

img

然而,通过使用绘图变换,我们可以很容易地给椭圆添加一个"旋转角”。

  convert -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

img

请注意,椭圆的 center(旋转点)在应用旋转之前首先被转换。然后,ellipse 在 “0,0” 处的转换位置被绘制。

上面还显示了两个新的 MVG 绘图原语。‘push graphic-context’ 和 ‘pop graphic-context’。在上面的例子中,并不是严格意义上的需要,但在进行主要的绘图转换时,建议使用这两个原语。

pushpop 原语的作用是保存当前的绘图状态或 “graphic-context”,然后再次恢复。在这两个原语之间更改的任何绘图设置都会被遗忘。这包括曲面变形(如 “平移 “和 “旋转”)、颜色设置 fillstroke 或任何其他修改了绘图 state 的设置。

这些原语使您可以轻松地绘制具有许多变换的非常复杂的对象,然后将事物恢复到更 “正常"的状态,以便以后进行绘制操作。您可以在下面的绘制箭头中看到更实用的演示。

推/弹特殊对象 #

img 建设中 img

更多专门用于 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 的开发网站… http://www.linux-nantes.fr.eu.org/~fmonnier/OCaml/MVG/

阅读 MVG 文件 #

正如你在上面的例子中所看到的,MVG 的 -draw 参数可以变得很长。事实上,SVG 到 MVG 的转换可以产生一些非常长的 MVG 绘图参数(见下文)。

然而,IM 的一般命令行界面允许您通过使用 "@filename" 参数从文件中读取任何字符串参数。这很方便,因为这意味着你可以从一个单独的文件中读取非常长和复杂的MVG绘图命令。

例如,如果我将 MVG 操作放入一个名为 “draw_circles.mvg” 的文件中,那么我就可以像这样绘制…

  convert -size 100x60 xc:skyblue  -draw @mvg_circles.mvg  mvg_draw.gif

img img

不仅如此,ImageMagick 还懂得直接读取 “MVG:” 图像文件格式,让你可以更直接地绘制此类命令。然而,除非 MVG文 件定义了一个画布,否则你可能需要指定初始画布( -size 和”-background”)来绘制。

  convert -size 100x60  -background limegreen  mvg_circles.mvg  mvg_file.gif

img img img

你可以通过在 MVG 文件中添加一个 “viewbox”,并添加适当的背景色填充绘制,将初始画布设置移动到 MVG 图像中。这样就完成了 MVG 图像文件作为一个完整的图像定义。

  convert    mvg_circles2.mvg    mvg_image.gif

img img img

目前只有一种方法可以从 MVG 参数字符串中读取外部 MVG 文件,那就是使用 “图像 “绘图原语。遗憾的是,这将 MVG 的 include 转换为光栅图像,然后再将该图像覆盖到绘图表面。 换句话说,目前还没有MVG的 “include"功能。 :-(

img 建设中 img

您可以生成IM的低级绘制操作,使用 `+render 来记录它们。

当你再给 -render 设置/操作时,IM将立即绘制这些保存的操作。

奇怪的是,仅仅输出到 “MVG” 文件似乎也能做到这一点……。

     convert  ...   -draw '....'  draw_commands.mvg

注意:如果你在输出 MVG 格式文件时画了一条曲线,文件中会列出以下内容。曲线是一系列的短线段,而不是原始曲线。

当然您也可以完全使用更通用的 SVG 格式。参见下面的 “SVG 格式处理"。

MVG 阿尔法构成 #

img 建设中 img

我没有看到任何使用 Alpha 构图的情况(除了 painters 的算法之外)。基本上是一种 “over” alpha 合成)来绘制对象。

然而,这并不是说不能这样做。

如果你喜欢将你的矩形、椭圆、圆形或其他物体用不同的阿尔法成分(如 “DstOver”,这是一个类似于 Under 的成分),然后在空白的透明画布上画出你的人物,同样的构图,并将其合成到您的图像上。

然而,由于 SVG 允许您使用 alpha 合成来绘制文本和其他的图像,因此,您可以使用 SVG 来绘制图像项目到图像上,我想这将是一个未来的补充。

敬请期待

绘制符号 #

有时你在图像上有一组点,你想在那里绘制参考符号,如十字、圆圈等。遗憾的是,目前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"

  # convert 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.
  convert -size 100x100 xc:white \
          -draw "fill none stroke red   polyline $points " \
          -draw "fill none stroke blue  $crosses " \
          points_plus.gif

img

上面使用 “tr” 将每个点(两个数字)分成每条线上的一个点,然后使用 “awk” 进行所有数学计算,在给定的点上绘制"加号”。你可以使用任何你喜欢的东西,因为我只是在输入点列表上应用了一种文本宏扩展的形式。几乎所有的编程语言都可以做到这一点。对于上面的shell脚本案例,我只是发现 “awk” 是最简单、最快的手段。

其实你甚至可以使用 Imagemagick 本身的 “convert” 格式选项来进行这种 “macro” 的扩展…比如这里我用它来计算圆周上的一个点,对于这个"点符号”。

  # 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

  # convert 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=$(convert 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.
  convert -size 100x100 xc:white \
          -draw "fill none stroke red   polyline $points " \
          -draw "fill none stroke blue  $circles " \
          points_circle.gif

img

现在,您生成的绘制字符串可能会变得相当长,并且可能会开始导致您的最终命令的长度问题。所以,与其将点转换成长字符串,然后我们在命令行上传递给 IM,不如将绘制命令以文件的形式管道化给 IM。

我这次也使用了 SVG 路径的绘制方法来代替 Draw Primitive 的绘制方法。另外我生成的符号是每个点周围的三角形。

  # 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"

  # convert 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 |\
    convert -size 100x100 xc:white \
          -fill none -stroke red  -draw "path 'M $points' " \
          -fill none -stroke blue -draw '@-' \
          points_tri.gif

img

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 "'"
  ) | convert mvg:- points_cross.gif

img

这使用了特殊的shell编程技术,在shell括号中 “echo” 的任何东西都将作为MVG文件被输入到最终的"转换"命令中。第一个 “echo” 定义并填充图像的绘图画布,而 “while” 循环则将每个 “点” 转换为一个给定半径的圆。

这种方法的优点是,你不会受到任何字符串的限制,而使用其他两种方法可能会受到限制。

其他你可以生成的符号包括方框、钻石、错误条等。

也请参见下面的"绘制圆圈”,了解其他圆圈方法,包括不计算相对 “路径"的圆圈绘制。

绘制符号的替代方法 #

除了直接绘制符号外,还有其他的方法可以将符号添加到图像中。

符号字体 #

您可以从 Symbol Font 中提取符号,并将其保存为一个小位图。你也可以使用小的预定义但色彩丰富的图像来做这件事。

然而这样做可能会出现问题,无法准确定位字体相对于特定像素的位置。也就是说这不是一个非常精确的技术。但是你可以在任何像素位置组成任何图像。例如这些符号是从一些字体中提取出来的,用于这些示例页面的具体使用。

<=   =>   x   +   +   +   o   o   o   o

分层图像一节中给出了将图像合成到大背景上的例子。然而,循环的方法可能更有用,例如在分层图像的程序化定位中给出了。

未来:使用坐标对图像进行分层的例子

形态学 #

另一种选择是使用形态学,使用特殊的"形状"内核,如 “磁盘”、“环形 “和 “加”,甚至是你自己的用户定义内核,来 “稀释"单个像素。

例如…

  convert -size 80x80 xc:black -fill white \
          -draw 'point 20,15 point 55,30 point 40,60'  points_pixels.gif

  convert points_pixels.gif -morphology Dilate Ring    points_rings.gif

  convert points_pixels.gif -morphology Dilate Plus:4  points_pluses.gif

  convert points_pixels.gif -morphology Dilate Cross:3 points_crosses.gif

img img img img img

然后可以通过使用 Alpha Shape Operator 将结果直接转换为彩色叠加。

这样做的最大好处是,你其实不需要知道每个符号的单独位置。或者有多少个符号。但这也可能是一个缺点。一个主要的缺点是,位置只在整数位置。你不能使用浮点 “子像素"定位来"绘制”。

卷积 #

一个几乎相同的技术是使用 Convolve,使用专门设计的内核,它允许你设置不同的灰度,而不仅仅是一个简单的开/关结果,如上所述。

通过使用不同的用户定义内核,为图像的每一个通道(红、绿、蓝和阿尔法),甚至可以从每个像素坐标创建多色符号。

为此,我使用了一个我写的特殊脚本 image2kernel 来将彩色图像(见右图)转换为每个通道的独立浮点卷积核。

  image2kernel -q marker.png marker.dat

img

这将生成四个文件,比如 marker_R.dat,每个通道都有一个非常小的输入图像,这是用户定义的图像表示(原点在图像中心)。

现在使用这些内核数据文件,我们可以将这些单点在透明的背景上 Convolve 成我们彩色的标记图像。

  convert 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

img img img

在IM v6.7.6-9之前,Combine Operator 要求图像的透明度通道以 “哑光"值而不是alpha值的形式给出,因此,由此产生的alpha通道需要被否定。EG:

  ... "`cat marker_A.dat`" -negate \) \

只能使用小图像,像素点要足够分散,符号不要重叠。这是因为 Convolve 会将重叠的区域加在一起,使其比预期的更亮。

以上内容已被转换成UNIX shell脚本 convolve_image,以方便使用。

  convolve_image  points_pixels.gif marker.png   point_markers.png

这个技术源于IM论坛上的一个讨论-IM 的有趣体验。用户希望在足球场的背景图上放置小人,让他们的位置在图片中拼出一个人的名字。

分层 #

一种不同的技术,如图像层,使用你从源图像中提取的像素列表进行定位,可能是更好的方法。你可以先叠加更远的符号图像,然后再叠加前景图像,你可以通过编程选择或随机选择什么符号替换什么点。

关于这个例子,请看地图中的图钉

绘制圆圈 #

绘制选项为您提供了许多方法来完成一些非常基本的工作。绘制圆。

例如,您可以在圆周上的任何一点上画一个圆,因此您需要计算一个中心点和一个半径为25像素的第二点。因此,您需要计算一个中心点和第二个点,这个点的半径(例如25像素)离第一个点的距离。

  convert -size 100x60 xc:  -stroke Firebrick  -fill tomato  -strokewidth 2 \
          -draw 'circle 50,30 50,55'    circle_circle.gif

img

Fred Weinhaus 指出,通过使用平移,你可以消除计算圆边坐标的需要,而直接给出半径即可。

  convert -size 100x60 xc:  -stroke SeaGreen  -fill PaleGreen  -strokewidth 2 \
          -draw 'translate 50,30 circle 0,0 25,0'    circle_circle_trans.gif

img

但是当绘制多个圆时,上述操作需要对每个圆进行单独的 -draw 操作,或者使用 Context Pushing

使用椭圆可以直接指定半径为轴长

  convert -size 100x60 xc:  -stroke Sienna  -fill Wheat  -strokewidth 2 \
          -draw 'ellipse 50,30 25,25 0,360'    circle_ellipse.gif

img

你也可以用 stroke-lineecap round 画一条非常非常短的线来生成一个圆。描边的宽度可以设置圆的直径。注意线条必须有一定的长度(无论多小),否则画不出任何东西。

  convert -size 100x60 xc:  -stroke Blue  -strokewidth 50 \
          -draw 'stroke-linecap round line 50,30 50,30.0001' \
          circle_line.gif

img

这种技术,不幸的是不能勾勒出生成的圆,但对于覆盖大面积的区域,大笔触宽度是有用的。请看下面一些简单的例子。

这种方法利用了 SVG 路径的绘制方法,所以可以在不需要计算任何额外坐标的情况下绘制圆形。

  convert -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

img

只有最初的绝对移动 ‘M’ 是用来定义中心的,接下来的路径组件中的 ‘25’ 和 ‘50’ 是定义相对于这个中心的圆的半径和直径。

在IM v6.4.3-5之前,相对移动SVG路径项 ’m’ 是被破坏的。 如果您的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

  convert -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

img

如果画一个精确的圆并不重要,你可以使用这个4 Bezier段 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

  convert -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

img

如果你喜欢一个完全相对于中心起点绘制的,你可以使用这种技术。只使用半径值,使其生成简单,只使用 API 中的字符串函数。

  convert -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

img

你能想到其他画圆的方法吗?

绘制箭头-定位、旋转和缩放符号 #

使用上述技术,你可以创建一个特殊的符号,如箭头,你可以定位,使其点在线的最末端,并画在它上面。如果你画的箭头后的线(典型的情况),那么箭头将被绘制在该行的顶部。

然而,他们有三种类型的箭头,可以定义,每个类型的定义在不同的方式取决于它的使用。

  • 测量时,你只是想用箭头头标记线的两端,以指示一些工程图中的测量极限。非常简单。
  • 矢量,显示一些数值的方向和强度。例如在天气风图中。需要一个尾巴,0,0点就是尾巴的末端。通常情况下,这样的向量会形成一个大网格。
  • 指标,指出一些细节。对于这个0,0点可能应该是箭尖,或者是箭头本身前面的一些距离。

测量箭头 #

简单地在一条线的末端添加一个箭头是比较容易做到的。基本上,您可以创建一个 ‘arrow head’ 符号,并将其画在正确的位置。

例如…

  arrow_head="l -15,-5  +5,+5  -5,+5  +15,-5 z"

  convert -size 100x60 xc: -draw 'line 10,30 80,30' \
          -draw "stroke blue fill skyblue
                 path 'M 80,30  $arrow_head' " \
          arrow_horizontal.gif

img

请注意,我画的符号,使其起点是线的最末端。这样一来,它就可以在之前画好的线上面向后画,形成一个非常漂亮的整齐的符号。

箭头却有一个相关的方向。你可以在许多不同的角度创建大量的箭头定义,许多程序都这样做。但是既然箭头是一个矢量,那么为什么不把箭头作为一个矢量进行旋转呢。IM绘图命令内置了绘图旋转(Canvas Warping),所以让我们使用它们。

这样做还有一个好处,就是把位置从箭头头的 path 定义中移出来,让你把整个路径指定为 constant

  arrow_head="path 'M 0,0  l -15,-5  +5,+5  -5,+5  +15,-5 z'"

  convert -size 100x60 xc: -draw 'line 25,55 70,10' \
          -draw "stroke blue fill skyblue
                 translate 70,10 rotate -45
                 $arrow_head
                " \
          arrow_rotate.gif

img

如果你喜欢改变箭头的大小,可以在旋转后增加一个 scale 绘制选项。

  arrow_head="path 'M 0,0  l -15,-5  +5,+5  -5,+5  +15,-5 z'"

  convert -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

img

请注意,它是如何放大离开箭头的"尖端”,你指定的地方。这是处理箭头的一个非常重要的方面,因为它是唯一的终点,和角度的线,你正在添加箭头的事项。

transforms 的顺序是很重要的,而且实际上与它们实际被处理的顺序是相反的。也就是先将比例尺应用于座标,然后是旋转,再是平移。如果座标变换不是按照这个顺序进行的,我们最终也会对箭头的最终位置进行缩放,而不是我们期望的位置。

另外由于比例尺有两个数字,而原来的箭头头符号是水平设计的(角度为零),所以可以分别将箭头的宽度与高度进行比例。同时注意笔画宽度也随着箭头的大小而缩放,保持一致。

  arrow_head="path 'M 0,0  l -15,-5  +5,+5  -5,+5  +15,-5 z'"

  convert -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

img

现在,当你在画布上翘起一个个箭头时,也许还有许多其他的绘画操作,你可能喜欢在一次 ["-draw"](https://imagemagick.org/Usage/option_link.cgi?draw) 操作中把它们全部画完。说要画线,然后在两端添加箭头,需要不同的颜色、位置、旋转,甚至可能是不同的比例。这意味着我们需要将画布翘曲的范围限制在每个单独箭头头的绘制上。如果你不限制范围,你可能会在以后开始影响到其他后面的绘制操作,永远不能很确定你生成的是什么。

为了限制翘曲的范围(以及所有其他的绘图属性),您将涉及到的部分包裹在一个 graphic-context

  arrow_head="path 'M 0,0  l -15,-5  +5,+5  -5,+5  +15,-5 z'"

  convert -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

img

push 主要是将当前所有的绘图属性保存起来,以备将来使用,而 pop 则恢复这些属性,用之前保存的设置替换任何设置(颜色、扭曲、位置等)。这意味着在 ‘popping’ 之后,‘canvas warp’ 会被取消,画图会回到修改之前的状态。

上述技术只是生成箭头的一种方法,在绘制箭头作为测量距离的一部分时,如在技术图纸中,是一种很好的方法。

矢量箭头 #

如前所述,矢量既显示方向,又显示某个数值的强度。这意味着箭头的长度是可变的,箭头头可以在任何位置远离向量的起点。

现在,你可以做一些沉重的数学计算的位置,箭头头应该是地方给定的向量的长度和角度,但他们是一个更好的方法,这让 ImageMagick 为你做这些计算。

解决的办法是在 Warped Canvas Space 中画一条长度合适的水平线作为向量长度。当这条线画好后,只需将绘图空间再次转换到线的末端,而画布仍然是"扭曲的”(warped)。现在你的位置已经正确了,只要正确的旋转就可以像正常的那样画出矢量的 'arrow head'

例如,这里我以-35度角生成一个70像素长的矢量。

  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 '"

  convert -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

img

指示箭头 #

在上面我还演示了一个指示箭头,指向之前矢量箭头的起点。

然而我没有像之前那样画箭头,而是将它创建为一个反向的箭头符号,它的起始点距离原点(或起始点)10像素。这是一个符号位于我想要指示的位置,所以我实际上并不希望箭头直接在那个位置上面,而是离它稍微远一点。

现在,虽然指示符比向量处理起来更简单,通常不需要可变长度,但你通常要在指示符的远端添加文字来指定指示的内容。和之前一样,计算这个位置可能很困难,何必呢。

文字定位的解决方法也和向量一样。保留原来用于绘制指示箭头的翘曲空间,并将原点转换到该箭头的尾端(在翘曲空间中水平40像素)。现在我们已经重新定位了,我们可以围绕这个新的位置解除扭曲,这样你就可以像正常的那样绘制文本(有轻微的偏移)。

不幸的是,虽然默认的文字对齐方式是"左”,但目前你不能在 MVG 中指定文字对齐方式,作为重力的单独设置。如果这是一个问题,请在 IM bugs 论坛上提出请求,希望文本对齐(作为独立于重力定位)能够成为现实,尤其是它实际上是 SVG 规范的一部分。

绘制对象 #

宽广的色彩笔触 #

你不需要用路径或轮廓完全封闭填充区域,就能创造出各种形状。使用非常大和宽的笔触,你可以在画布上生成大面积的颜色和色块。

例如,一个宽阔的笔触椭圆弧形可以生成一个漂亮的颜色区域,我实际上已经看到用于创建海报。

  convert -size 100x100 xc: -fill none -stroke powderblue \
          -draw 'stroke-width 70 ellipse -30,0 90,90 10,50' \
          -rotate 180  arc_background.gif

img

或者你可以生成一个小丑相当复杂的笑容。

  convert -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

img

你能想出什么办法?请告诉我们。

圆柱体 #

IM 论坛的讨论中,有一个关于使用 ImageMagick 绘制命令绘制圆柱体(特别是阴影圆柱体)的重要讨论。

绘制圆柱体的诀窍是绘制 roundrectangle 原语,使其末端形成椭圆形。也就是说,如果圆柱体的宽度为 50 像素,则将矩形的角分别舍去 25 和 12 像素。这就是矩形宽度的一半,然后再减半。

这样,一个圆柱体就变成了两个相互叠加的圆角矩形。第二个颜色填充的 'end oval' 的大小正好是两个角的两倍。例如…

  convert -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

img

通过将第一种填充色替换为渐变色(使用记忆中的平铺技术),你可以让圆柱体看起来更像3D……。

  convert -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

img

通过慢慢完善圆柱体的绘制(如IM论坛中讨论的那样),你可以走很长的路来生成非常复杂和具有视觉吸引力的圆柱体。这包括增加封闭半透明玻璃圆柱体、阴影效果和标签。

该讨论的最终结果是一个脚本 “cylinder_bar",生成一个圆柱体百分比条……

  cylinder_bar 95 cylinder_95.png

img

该脚本可以生成任何尺寸的图像,根据该尺寸和脚本顶部定义的其他设置适当调整所有参数。还包括 glass thickness 的概念,在一个封闭的半透明玻璃圆柱体和内部的彩色圆柱体之间创造一个间隙。

请注意圆柱体非常微妙的阴影,特别是当绿色圆柱体的末端与玻璃圆柱体的末端重叠时。只要稍加预想,就能做出惊人的效果。

在文字串中绘制特殊字符 #

引号还是反斜杠? #

人们在使用 -draw 时遇到的最大的问题之一是绘制字符,这些字符对 UNIX shell 和 DOS 命令行甚至其他语言如 C、Perl、PHP、R 或 Visual Basic 都有特殊意义。

在这方面最大的罪魁祸首是两种类型的引号字符,以及变量替换字符,如美元 '$' 和 shell 和 ImageMagick 的转义字符,反斜杠 '\'

基本上作为 -draw 的MVG参数需要加引号,而里面的 'text' 字符串参数也可能需要一些额外的引号。

为了解决这个问题,用户通常会使用两个不同的引号字符,一个用于 shell,另一个用于 MVG 文本字符串。

-draw '... text 0,0 "string" ...'

需要注意的是,对于 windows 用户来说,这是唯一真正的选择,它有自己的引号问题和方法。或者他们会交换引号,用…

-draw "... text 0,0 'string' ..."

它允许你包含 shell 变量替换(使用 '$' 而不进行转义。)

选择正确的形式可以解决大多数问题,但有些字符仍然存在困难,每个解决方案都取决于你到底使用哪一组引号,因为它们也定义了特殊字符应该如何转义。

以下是四种情况下的引号,以及特殊字符的处理…

  • 对 shell 参数使用单引号,对 MVG 文本字符串周围使用双引号。

处理绘制文本字符串的最简单的技术是为包装 shell 参数使用单引号。然而,这意味着要在绘制的字符串中包含一个撇号,你需要离开 shell 的"单引号模式”,并在 shell 的单引号之外提供撇号。

例如,这里是如何处理我提到的四个特殊字符。

  convert -size 250x50 xc:none  -box white  -pointsize 20 -gravity center \
          -draw 'text 0,0 "  '\''  \"  $  \\  " ' \
          -trim +repage  text_special_sd.gif

img

请注意,由于美元符号不需要转义,你也不能用它来替代 shell 变量的内容。

重要的是要记住,反斜杠是IM绘图字符串处理的唯一特殊字符。同时,它存在的原因也纯粹是为了让你可以转义任何"IM 绘制字符串引号”,比如我们在上面使用的双引号。除此以外,其他所有的怪异都是由 UNIX 命令行 shell 引起的,而不是 IM。

PC-DOS 有它自己的怪异之处,我希望在使用环境中的IM时,能对特殊字符进行转义。

  • 在 shell 参数中使用双引号。 在 MVG 文本字符串周围使用单引号。

如果你确实想在绘制的字符串中插入一个 ‘shell variable’,那么你将不得不在 shell 参数的外面使用双引号。这使得整个事情变得更加复杂,因为你失去了 shell 的保护,你现在不仅要转义美元 '$' 符号,而且还要转义反斜杠 '\'

另一方面,shell 将不需要使用单引号字符作为它的参数结束限制字符,所以这方面被简化了。让我们总结一下我们的特殊字符短名单的结果。

  convert -size 250x50 xc:none  -box white  -pointsize 20 -gravity center \
          -draw "text 0,0 '  \\'  \"  \$  \\\\  ' " \
          -trim +repage  text_special_ds.gif

img

请注意,如果你想画一个反斜线本身,MVG 文本字符串需要将反斜线加倍(如前面的例子),但是 shell 本身也需要将每个反斜线加倍,总共产生四个反斜线才能产生一个这样的字符。

这种翻倍很快就会让人不知所措,需要大量的反斜杠才能达到你想要的效果。只要采取的是慢慢的、简单的方法,你就会针对自己的情况想出办法。

  • 使用单引号进行 shell 论证。 与 MVG 文本字符串周围的单引号。

最后,让我们总结一下最后两种引号组合。我将让你去弄清楚它们是如何被 shell 和 MVG 解码的。

  convert -size 250x50 xc:none  -box white  -pointsize 20 -gravity center \
          -draw 'text 0,0 '\''  \'\''  "  $  \\  '\'' ' \
          -trim +repage  text_special_ss.gif

img

  • 在shell参数中使用双引号。

在 MVG 文本字符串周围加上双引号。

  convert -size 250x50 xc:none  -box white  -pointsize 20 -gravity center \
          -draw "text 0,0 \"  '  \\\"  \$  \\\\  \"" \
          -trim +repage  text_special_dd.gif

img

正如你所看到的,来自命令行的 -draw 参数既要处理命令行 shell,也要处理 MVG 文本字符串中的反斜杠和引号转义。其结果可能会让人感到困惑和棘手。只要记住,shell 对这两种引号的处理方式不同,而 MVG 文本字符串则不同。

当然,在复杂的脚本中,更好的方法可能是完全避免 shell 和任何脚本问题。你可以通过读取 MVG 绘图文件中的 -draw 参数来实现。

-draw @drawfile.mvg

当然,你仍然需要对你使用的任何引号字符进行反斜杠处理,以及对文本中的任何反斜杠进行处理。然而,这比起同时处理 shell 自己的引号和转义系统要简单得多。

  convert -size 500x50 xc:lightblue  -font Candice -pointsize 36 \
          -gravity center     -draw @text_quotes.mvg      text_quotes.gif

imgimg

第一张图片来自我使用的一个 “MVG” 文本文件。它不包含转义符或引号。因此,只有 MVG 引号和转义符是存在的。

请注意,在上面的例子中,如果我对 MVG 文本字符串使用了单引号,唯一的变化是我需要对字符串中的单引号字符进行反斜杠处理,而不是双引号字符。

关于百分比字符 #

最后一点是关于 "-draw text" 操作符中的特殊 escape 字符。百分号字符 "%" 应该"按原样"绘制。你不需要做任何特殊的操作来绘制它们。如果它们不能"按原样"绘制,那么你的IM版本较旧,应该尽快升级。

直到IM 6.2.4版本,"%" 字符被用作转义字符,在绘制的文本字符串中包含额外的图像信息。现在不再是这样了,因为当SVG图像也试图绘制百分数字符时,这种转义符是混乱和不正确的。 百分号 “转义符 “的使用(以及”/n “换行符)被认为与 -draw 操作符和MVG格式处理SVG图像格式的预期用途不兼容。因此,从 IM 6.2.4 版本开始,%转义就不适用了,反斜杠只能转义自己和周围的引号。

    convert -size 250x50 xc:none -box white  -pointsize 20 -gravity center \
            -draw 'text 0,0 "%w\n%h"'    -trim +repage text_escapes.gif

img

关于"百分比错误"的更多细节,以及在旧版 ImageMagick 中使用 -draw 时避免该错误的方法,请参见 Drawing a Percent Bug 页面。

用注释代替绘制 #

避免这类问题的较好方法是使用 [-annotate](https://imagemagick.org/Usage/option_link.cgi?annotate) 而不是 draw 来绘制文本。这个操作符是 draw 操作符的一个封装器,可以使用 draw 的所有功能,但形式更简单。

基本上这个操作符只需要一组引号(对于 shell)。这使得处理特殊字符变得更加简单。

不幸的是,虽然你不再需要为 IM 转义引号,但你现在有百分比转义,如 '@' 文件读取,'\n' 换行,以及其他百分号转义扩展。

例如,使用单引号…

    convert -size 200x50 xc:none  -box white  -pointsize 20 -gravity center \
            -annotate 0 '\@  '\''  "  $  \\  %% ' \
            -trim +repage  annotate_s.gif

img

而对于双引号…

    convert -size 200x50 xc:none -box white -pointsize 20 -gravity center \
            -annotate 0 "\@  '  \"  \$  \\\\  %% " \
            -trim +repage  annotate_d.gif

img

然而,如果你使用 '@' 转义符从文件中读取字符串,所有的注释引号和转义符将被完全忽略。

例如,我们在这里包含了一个图像的宽度和高度的信息!

    convert -size 200x50 xc:none -box white -pointsize 20 -gravity center \
            -annotate 0 '%w\n%h' -trim +repage    annotate_percents.gif

然而,当从文件中读取注释字符串时,所有的转义都会被完全忽略。

    echo -n '@ %w\n%h' |\
      convert -size 200x50 xc:none -box white -pointsize 20 -gravity center \
              -annotate 0 '@-'  -trim +repage  annotate_file.gif

img

更多信息请参见 Annotate Text Drawing Operator,特别是 Annotate Escape Characters

IM 和 SVG 处理 #

SVG 输入驱动。RSVG 与 MSVG #

处理实际的 SVG 图像格式是一项非常复杂的工作。引擎需要处理 SVG–可缩放矢量图形文档所定义的所有方面。这需要大量的编程工作和时间。

因此,ImageMagick 在 SVG 格式图像的处理上提供了两种方法。第一种是使用一个开源的 RSV G库,将 SVG 格式转换成 IM 没有问题的光栅图像。这个引擎几乎在 SVG 处理的所有方面都是完整的。

第二种方法是IM尝试将 SVG 转换为 MVG,使用一个名为 MSVG 的内置IM方法。MSVG 试图将SVG图像转换成IM的 -draw 运算符 “MVG"绘图语言。绘制 MVG 的很多功能都是专门为此而创建的。不幸的是,虽然基本的线条绘制和着色功能是存在的,但它远不是一个完整的 SVG 转换器。

你可以通过使用特殊的输入格式 “MSVG:"(IM v6.3.4新增)读取 SVG 图像来强制使用内部的 MSVG 转换器。但如果 RSVG 库存在,大多数 ImageMagick 将使用它来渲染 SVG 图像。

要找出你的 IM 会做什么,请用…

  convert -list format | grep SVG

从括号中的 “RSVG” 可以看出,我自己的 IM 将使用我电脑上的 RSVG 库,并给出了版本。

在这里,我"绘制"了一个小的、手工制作的 SVG 图像 “diagonal.svg"(由论坛用户 penciledin 贡献),它在白色背景上创建了一个简单的对角线渐变的矩形。

  convert diagonal.svg  diagonal_rsvg.gif

img

完美的。一个正确的对角线梯度被生成。

然而,如果你使用内部的 MSVG(如果没有 RSVG 库,则为默认值)来渲染…

  convert msvg:diagonal.svg  diagonal_msvg.gif

img

正如你所看到的,内部 MSVG 转换失败,返回的是垂直梯度而不是对角线。

  convert msvg:diagonal.svg diagonal.mvg

你大概可以看到MSVG转换器是如何尝试将 SVG 转换为 MVG 绘图命令的。

当前内部 MSVG 已知失败的地方包括…

  • 非垂直梯度(没有转换到新的MVG梯度处理)
  • 沿着弯曲路径的文字
  • 文本对齐(与重力分开)。

然而大多数基本的绘图动作都被处理了。

还请记住,MVG 语言实际上可以处理 SVG 不能处理的事情,包括使用重力来定位图像和文本。重力不是 SVG 规范的一部分,尽管它是 IM 文本和字体处理的一个组成部分。

另外请记住,MVG 没有 SVG 所拥有的容器机制。内部的 MSVG 转换器用图形上下文的推送和弹出代替了 XML 容器(见上面的 MVG 输出),效果是一样的。 SVG 的设置

SVG 图像格式是一种矢量格式(请参见关于矢量图像格式的一句话),因此图像通常没有一个默认的"大小”,而是以特定的 "-density" 来 “绘制"或"渲染”,就像 postscript 一样(默认密度是72dpi)。

另外,如果 SVG 没有 “绘制"背景,您可以通过使用 -background 设置指定要使用的背景颜色。

例如这里是另一个小的 SVG 图像 “home.svg”,它已经使用3种不同的密度,3种不同的背景进行"渲染”,包括一个透明的背景。

  convert -density 36                      home.svg  home_1.gif

  convert              -background skyblue home.svg  home_2.gif

  convert -density 144 -background none    home.svg  home_3.png

img img img

请注意,我使用了 PNG 格式的图片来制作上面例子中较大的透明背景版本。由于半透明的边缘像素,这产生的图像比 GIF 图像格式产生的图像更干净。当最终图像中涉及到透明度时,总是建议使用 PNG。

我发现有些 SVG 图像不能缩放。也就是说,它们被定义为"像素”,而不是现实世界中的长度,如"点”、“英寸"或"毫米”。因此,虽然 -density 设置可能会改变图像的整体大小(以现实世界为单位),但 “像素 “的大小不会改变,因此图像本身的大小也不会改变。然而这样的SVG图像是相当罕见的。

更糟糕的是,一些 SVG 图像使用了"像素"和 “点"的混合测量,除非作者故意这样做,否则你可能会得到一个真正的混乱,你可以尝试使用不同的密度,而不是作者想要的。幸运的是,这种情况更加罕见。

一个简单的解决方法就是将 SVG 中所有的 “像素"单位改成"点”,但也不能盲目的使用,以防故意使用"像素”。

SVG 输出处理 #

从 IM v6.4.2 开始,IM 可以将任何位图图像转换为 SVG 矢量图! 转换并不总是成功的,但较大和/或较简单的图像(如位图蒙版)会转换得很好。

例如,我在这里将一个可怕的位图形状转换为SVG图像,然后再将其转换回来,以便将位图平滑为一个适当的反锯齿形状。

  convert -pointsize 72 -font Candice label:A -threshold 50% \
          -trim +repage -bordercolor white -border 5x5 A.gif

  convert A.gif  A.svg

  convert A.svg  A.png

img -> img

然而,要使之工作,必须安装"开发"的 AutoTrace 库,并在IM中配置 -with-autotrace 开关。

如果没有安装 AutoTrace 库并将其编译到 IM 中,那么生成的 SVG 输出将是大量的单像素圆圈,生成一个二进制结果,而不是一个平滑的 SVG 轮廓图像。这样的图像比较巨大,通过 SVG 渲染往往需要很长的时间来渲染。

其实需要一种更好的默认栅格到矢量的技术,可能会使用 Morphology skeletion 和 MAT 技术。

有一个 autotrace: 输入代理,来"平滑输入位图图像”,直接使用 “autotrace” 命令一次性完成上述所有步骤。然而我最后一次看到这个代理已经消失了。

你应该这样使用它…

  convert autotrace:A.gif  A_traced.png

img

当然这并不能让你从 “autotrace” 命令中得到 SVG 输出,只是通过 SVG 过滤输入的图像来平滑它。

作为一种替代方法,你可以直接使用 “autotrace” 命令,如 Raster to Vector EdgingSkeleton using Autotrace 示例所示。

你也可以看看 cancerberosgx 的结果,在生成 SVG 图像,他研究了转换照片的解决方案。

非IM矢量图形编辑器 #

ImageMagick 是一个像素数组处理器,它一般不会保存矢量图像(‘MVG’ 是唯一的例外),只会读取图像并将其转换为像素数组。

其他像素图像编辑器也是如此,如 Gimp、Photoshop 等。

对于编辑和处理基于矢量的图像,可以使用以下程序,如

  • Sodipodi 基于SVG的矢量图形编辑器。
  • Xfig 简单但非常好的矢量对象编辑器。(适用于标志、地图和在页面上排列照片)
  • Dia
  • AutoTrace 将位图数组中的形状转换为矢量轮廓。
  • Sketch 基于 Python 的曲线文字矢量编辑器。

当然,这不是一个完整的列表。即使是许多文字处理程序,如 OpenOffice、Word 和 TeX,一般都有各种简单的,虽然往往难以使用的对象编辑器。

然而对于一般将矢量图形格式转换为不同的矢量格式,不要使用 ImageMagick。ImageMagick 本质上是一个光栅图像或位图图形转换器和操作器,而且永远都是这样。更多信息请参见 A word about Vector Image formats