ImageMagick v6 Examples --
Drawing

Index
ImageMagick Examples Preface and Index
ImageMagick Draw Commands
MVG - Magick Vector Graphics
MVG file Handling
Stroke and StrokeWidth
Drawing Lines, Line Width and Stroke
Drawing Symbols as a List of Points
Drawing Circles
Drawing Special Characters in the Text String
IM and SVG Handling
Non-IM Vector Graphic Programs
Some simple examples

Drawing in IM is the way to add new elements to IM. While a lot of text drawing is covered in the examples page for Compound Font Effects, and in Image Annotating, this page deals with the other more general aspects of the "-draw" operator.

The draw command started as a means to create simple images. Now it's the command line interface to the vector graphic operators used by IM.


ImageMagick Draw Commands

Images in computers are generally saved in two different ways. The first and most common way you have seen throughout these example pages is known as Raster Graphics. In this approach, images are stored in terms of a rectangular array of pixels.

The other way is less common, and less modifiable, but in another sense more versatile, Object Vector Graphics. In this form the image is described in terms of lines, arcs, color fills, and sometimes depth. This is useful because you can scale these images to just about any size you want and they still display perfectly. You can also describe very large and complex images in a very small amount of space when compared to the raster format equivalent.

Examples of vector graphic images include postscript, and the new SVG -- Scalable Vector Graphics.

True-Type Fonts are also examples of vector graphics, as this allows the individual character descriptions to be used at any scale.

The "-draw" image operator, is a window into the ImageMagick vector drawing functions, and forms a set of commands quite separate from the normal command line image operators of IM.

There are only a few vector graphic file formats in general use as every such format is usually very different from other such formats. The result is that there is very little in the way of code sharing possible.

For this reason, ImageMagick is more concerned with the use of vector graphics to draw SVG format images. Postscript and true-type font graphics are passed to other external 'delegate' libraries and applications that are much more suited to drawing those kinds of vector graphic formats.

That is not to say that delegates are not available for SVG. One example is a RSVG library or GTK SVG library which is available at compile time. IM will link to those libraries to convert SVG rather than attempting to do it itself.

Primitive Draw Commands

Lets start with the oldest, simplest, and most common drawing primitives of the "-draw" image operator of MVG commands.

Note that all arguments are treated as floating point, and do not have to be integers, such as I typically use in these examples.


  # Single Pixel Draw  (two ways -- these have been enlarged)

  # 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 (see below)
  convert -size 10x6 xc:skyblue  -fill black \
          -draw 'color 6,3 point'   -scale 100x60   draw_color_point.gif
[IM Output]
[IM Output]

  # Rectangle  /  Rounded Rectangle  /  Rectangular Arc

  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
[IM Output]
[IM Output]
[IM Output]
[IM Output]

  # Circle  /  Ellipse    (centered on a point)

  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
[IM Output]
[IM Output]
[IM Output]

  # Line / Polyline / Polygon / Bezier

  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

[IM Output]
[IM Output]
[IM Output]
[IM Output]

  # 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
[IM Output]
[IM Output]

These last two fill type operations are currently the only draw operations that are affected by "-gravity". Other modifiers for these operations, include: "-fill", "-tile", "-origin", "-stroke", "-strokewidth", "-font", "-pointsize", "-box",

Their are other modifiers but these are related to the more advanced Magick Vector Graphics language.

Bezier Primitive

The 'bezier' primitive is used to draw curves. Each command will draw just one curve segment. Typically 4 points (8 numbers) is given: a start point 'knot', two control points and an end point 'knot'. The two control points define the direction and how fast the curve deviates from the attached end 'knot' points.

To join two curves smoothly, the control point from the end should be mirrored through the 'knot' to form the control point in the next Bezier curve.

For example here I draw two bezier curves that join smoothly together. Note how the control lines and points (also drawn) mirror straight though the join coordinate, both in angle and in length. This is important or the curve will not be smooth.

  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
[IM Output]

If I move one of the control points, so that it is NOT 'reflected' though the attached 'knot' from the other control point of the same 'knot', then the curve will be dis-continuous.

  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
[IM Output]

If the control point is moved again so that it matches the related 'knot' point the line will come directly from that point without any 'curve' at all.

  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
[IM Output]

If both control points are set to the their respective 'knots', then a straight line will be generated.

  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
[IM Output]

The 'bezier' primitive is not really useful without specifying all 4 points. Only the first and last point are classed as 'knots' through which the curve will pass (or end). All the other intervening points are regarded purely as control points, effecting the curve in the sequence given, the further away the control point is the larger its effect on that segment of the curve.

  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
[IM Output]

It is not recommended that you use more or less than 4 points per 'bezier' curve segment, to keep things simple.

Actually I recommend you don't use the 'bezier' primitive at all, but use the SVG Path Cubic Bezier instead for generating curves. This has a special 'S" curve continuation function that automatically does the appropriate control point 'reflection' to generate smoothly joining curve segments and reduces the number of control points you need to use. You can also define points relative to the last end point in the path.

Color Fill Primitives

On top of the above 'simple' primitives, "-draw" also provides a set of color fill or modification primitives. These modify the color(s) in the image starting at the point specified, according to the method chosen.

These fill methods are actually not true 'draw' commands, but color replacement function. They were added to draw as it was the easiest place to insert their operations into ImageMagick in a very early version of the program.

Just as in the above, the color used is set with the "-fill" color setting, but if set, the "-tile" image will be used instead.

The other setting options above are not used, and have no effect on these operations.

Two extra settings as you will also apply to these primitives, "-bordercolor" and "-fuzz" factor settings. However these settings can NOT be defined within the 'MVG' language, so can only be set before using the "-draw" operator.

The first of these 'color point' you have already seen as an alternative to the 'point' draw primitive in the above examples. If you look carefully you will see the single white pixel we set in our test image.

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

However when drawing transparent and semi-transparent colors, these functions are not the same.

Here we have a three pixel red image (enlarged), the second or middle pixel we used the 'point' function to paint over the red pixel with a semi-transparent blue color, giving a purple result. If however use use the 'color point' function (last or right pixel), the red color is completely replaced by the semi-transparent blue pixel. It is not overlaid.

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

All the 'color' functions do full color replacement, while all other color primitive 'paint' the color on top of the image. As such you can use 'color' to draw the transparent color.

The 'color replace' draw function will replace all instances of the exact given color at the location specified. And as you can see the areas do not have to be connected.

  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
[IM Output]
[IM Output]

However as you can see in the first result, some pixels along the edges did not get replaced. These pixels are not exactly the same color as the pixel selected, so they were ignored. Adding a small fuzz factor will also include colors that are similar to the original color. As shown the second example above.

Of course a 'fuzz factor' is not a great solution, as it will not capture all such edge pixels. This is a recurring problem with all these 'color fill' methods, and one that has no general solution.

If you want to replace a specific known color, rather than select a color from the image itself, then the "-opaque" image operator can be used instead. This function also uses a "-fuzz" factor setting to increase the range of colors that match the given color.

The 'floodfill' method is also quite simple as it will just fill the the whole area around the point selected and not select any other area of similar colors which are not connected in some way.

You can also expand the area being filled by using "-fuzz" to include similar colors. In this case we chose a value high enough to also include the cross border, allowing the flood fill to 'leak' to the other side of the image.

  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
[IM Output]
[IM Output]

Flood-filling areas with a color is not without its problems. The color can leak across a thin boundary, into areas where it was not wanted, (see GIFs on a background pattern as a demonstration of this). Or it may not fill the area selected right to the edge, (see Anti-Aliasing and Flood Fill Problems). But it does work.

The 'filltoborder' is like 'floodfill' except your specify a color which borders the area to be filled, rather that the color to be replaced by the fill process.

Of course a fuzz factor is also recommended to include 'similar colors' in that border color selection, to further limit the floodfill.

  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
[IM Output]
[IM Output]
[IM Output]

The final draw color method is 'reset' which just replaces, or resets the whole image to the fill color. In this case the actual pixel selected has no bearing on the results at all.

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

This is actually very useful in that it gives one simple way of generating a plain solid color (or tiled image) canvas from an existing image. (See Canvases Sized to an Existing Image) for this and other ways of doing the same thing.

FUTURE: Using a "-tile" pattern to fill the area.

Matte Fill Primitives

The 'matte' draw primitive works in exactly the same way as the 'color' primitive described above, except it will not replace the color of the areas selected, only the 'matte' channel of the areas selected. (That is only the 'alpha' or 'matte' channel is adjusted by these fill functions).

Just as like the 'color' fill function, the 'matte' value uses the fill color (unless "-tile" as the source of the 'alpha value' to use.

Here we use the same 'color floodfill' example above, but here only adjust the matte channel to make the filled parts fully-transparent. That is the original color is still present, just transparent!

  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
[IM Output]
[IM Output]

The 'matte reset' function can also be used to make a whole image semi-transparent. Of course in this case we must output to PNG which can accept semi-transparent colors in images.


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

Notice that the 'black' color component was not used in operations, only the matte component of the color. The images original color is left as is.

FUTURE: Using "-tile" pattern for in interesting matte effect.

Note that both 'color' and 'matte' are full replacement of color functions, which will always produce a Boolean (all or nothing) type of color replacement. As such the edges of such areas will always show Aliasing effects.

Because of this, these are generally not good image operators for general image development, except for setting the transparent areas of GIF images (which are also Boolean). All is not lost however, as can be seen in the examples for Re-adding Transparency to an Image.


MVG - Magick Vector Graphics

As mentioned above the commands understood by the "-draw" had in the last few major releases undergone a major shift to form a special internal language in ImageMagick, called the Magick Vector Graphics language. For details see Summary of MVG Primitives and Syntax on the IM website.

These special drawing commands are designed with the goal of allowing ImageMagick to handle the even more complex SVG (Scalable Vector Graphics) language. It does this by attempting to convert images in the SVG format to the simpler MVG format, using an internal MSVG converter. For more details see SVG handling below.

Consequently what you saw above is only a tiny part of the capabilities of the "-draw" operator. Though if you want to draw complex objects, I do recommend you create a separate SVG format image of the object using an SVG editor such as "Sodipodi". (See Non-IM Vector Graphic Programs below).

Unlike SVG, MVG does not have any form of 'containers' or sets of image commands. These are all removed during the conversion process to produce a simplified sequence of MVG drawing commands.

Under Construction

Command Line Settings vs MVG Settings

First of all, almost all the settings you set via the command line options that the the draw primates use have direct equivalents in the MVG drawing commands. The main difference between setting the via a command line option, (such as "-strokewidth") or using a setting within a MVG drawing string (for example 'stroke-width), is that the MVG setting only lasts for the duration of the MVG command string.

Summary of the General Drawing Settings
  __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
These settings are usually well understood as they are regularly used and demonstrated above.

A font, stretch, style, and weight are used to identify a font from the ImageMagick font list. Most people however just select a specific font and pointsize to use instead. As such they are rarely used in IM.

As you can see the special settings for the 'color fill' primates do not have direct equivalents in the MVG. That is the "-bordercolor" and the "-fuzz" factor setting. These must be specified from the command line before using the "-draw" operator.

Some MVG settings would probably be useful as global command line settings, such as the 'decorate' setting for font drawing.

WARNING: "-gravity" is not part of the SVG specification. Within MVG it is only used for text and image placement, and justification. Their is currently no justification setting that is separate to the default 'gravitational' effects. However as justification is part of SVG text handling, that will probably change sometime in the future.

Now the global command line settings (outside the MVG draw string) are used to initialise the settings for each "-draw" operation you apply, which is why you can set a "-fill" colour which you can then use to draw a circle of that color.

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

You can override that global setting locally within the "-draw" MVG argument...

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

However settings set within a single "-draw" MVG argument only exist for the duration of that "-draw" operation. That is settings within a "-draw" are local only to that draw and do not carry into later separate "-draw" arguments.


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

If you plan to do a lot of operations, it may be better to do them all in the single MVG string, rather than multiple "-draw" operations.

  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
[IM Output]

MVG Specific Settings

Other MVG settings that control the way lines and objects are drawn are also useful to know even when using the primitive operations. These include..
   __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')
Remember a fill list of all MVG settings and drawing operators can be seen at Summary of MVG Primitives and Syntax in the IM website.

Lets look at the effects of some of the simpler settings...

  # 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
[IM Output]
[IM Output]

  # 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
[IM Output]
[IM Output]

  # 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
[IM Output]
[IM Output]
[IM Output]

The 'stroke-miterlimit' setting is harder to demonstrate. This property defines the angle at which a 'miter' is changed into a 'bevel' join. This occurs when the line join becomes 'too sharp' preventing the line extending too far beyond the join. Its value must be greater than 1.0.

The above shows how for the join angle I am display, the miter is converted to a bevel somewhere between a value of 6 to 7.

For example, a 'stroke-miterlimit' of 1.414 converts a 'miter' to 'bevel' for an angle of less than 90 degrees, a limit of 4.0 (the default) converts them for an angle less than approximately 29 degrees, and a limit of 10.0 converts them for an angle less than approximately 11.5 degrees.

SVG Path String

The SVG path is the basic drawing primitive of SVG. It is used to draw lines shapes, circles, curves, arcs and so on. The full specification of the SVG Paths can be found in the SVG Path Specification document.

This however is not a easy document to read as it is really for programmers, not users, so I'll simplify and summarize the path specification...

Note how you can specify things as either absolute coordinates or relative coordinates. Thus you can define an object in terms of relative coordinates and just supply an initial absolute 'move' coordinate to position the whole path.

On the other hand you can also use other 'graphic-content' commands to move a whole drawing within a 'viewbox' or 'translation', (see below). So really it does not matter if you use absolute or relative coordinates in SVG paths.

Moves, Lines and Path Closures are the initial starting point for learning about SVG object paths.

  # 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
[IM Output] [IM Output]
[IM Output]

Note however that 'Z' only closes the loop. It does NOT create a separate object. As such two 'closed' paths are still classed as being a single drawn object, weather they are overlapping or completely disconnected.

Here we show two closed but overlapping loops. As only a single path is used the object is a single object, and the 'fill-rule' setting controls how the overlapping region is to be filled.

  # 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

[IM Output]
[IM Output]

The 'fill-rule' is however sensitive to the direction in which paths are drawn. All the paths should draw in exactly the same direction relative to the 'inside' of the object.

For example here I draw the second object in the reverse direction to the first. As such when the two objects overlap that area is circled 'zero' times. That is it will be unfilled no matter what 'fill-rule' is used.

  # 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

[IM Output]
[IM Output]

This means that you can generate a 'holes' in an object, by reversing the direction, so as to keep the 'inside' of the object to the same side of the direction of travel.

  # 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

[IM Output]

The result is the same regardless of the 'fill-rule' setting, as the hole is both 'even' and 'zero' so is unfilled.

Of course if you use a completely separate 'path' element, you will generate a completely separate object. In which case, the 'fill-rule' does not apply and the objects are just drawn on top of each other, in the order given.


  # 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
[IM Output]

FUTURE: coordinate aligned paths  "H" and "V" 
Elliptical Arcs are the circle drawing function of SVG Paths...
The 'large' and 'sweep' parameters are especially important as they are used to determine which of the four ways you will 'arc' from your starting point to the finishing point for that path component.

'Large' is used for the longer path, while 'sweep' determined what side of the direct straight line path the arc should be drawn.

  #  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
[IM Output]
[IM Output]
[IM Output]
[IM Output]

Closing an arc with a 'Z' just draws a final straight line segment.

To create a full ellipse or circle you will need at least two 'arc' segments, both with the same 'sweep' setting, and one with the oppisite 'large' setting.

  # 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
[IM Output]
[IM Output]
[IM Output]

Note that if the line is too long to fit the given ellipse size at the angle given, the size of the ellipse will be enlarged to fit the line with the ellipse centered on the line.

This means that by using small numbers for the axis radii, you can just specify a ratio of axis lengths, and gurantee the direct line path goes though the center point of the ellipse. That is the path forms an elliptical diameter from one side of the ellipse to the other. This is not nessarilly the major or minor axis of the ellipse, just a elliptical diameter.

  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
[IM Output]

Of course using lengths of "1,1" results in a perfect half-circle, going from one point, one to the next point.

  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
[IM Output]

The SVG definition of 'arc' also declares that if either of the two radii are zero, then a straight line should be drawn. Thus "0,0" is just a simple straight line arc...

  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
[IM Output]

To finish off using arcs, lets give an example of typical use for them, namely, generating pie charts. Of course you may need to use some external trigonometric mathematics (how good was your senior high school math?) to determine the path points that are required.

  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
[IM Output]

Note that all the arcs are drawn to the left of the 'line path', and are flagged accordingly (using the 'sweep' flag). But if the arc covers an angle larger than 180 degrees, the 'large' flag needs to be set. see the last 'gold' component in the example above.

Also note that you should draw each section completely, even though this means you may have to draw bordering lines twice. If you don't you will probably either not completely fill that section with color, or the fill color will overlay a previously drawn section outline.

The only way to avoid doubling up multiple lines is to draw all the filled areas, then repeat so as to draw the outlines. That is you will need to drawing everything twice, ensuring things match up properly. Thus, doubling up of the outlines is probably the simplist solution.

Cubic Bezier curves can be defined using a 'C' function defining two control points, and the final end point. For continuing Cubic Bezier curves that use a mirror image of the last control point (for a continuous curve), you can use a 'S' function.

Here is an example. Because of the complexity of this function, I pre-prepared a canvas showing the location of the control points, as well as the 'assumed mirror' of the last control point.

  # 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
[IM Output]

The line connecting the control point to the final point on the path of that path segment (control line) basically defines the direction of the curve though that point on the path. A long control line will produce a smoother curve at that point, while a short line generates a sharper curve at that point. If the control point matches the point of the curve (control line is zero in length) the curve with have a sharp discontinuity at that point, as if only straight line segments were used.

As a more practical example, the following bit of code is extracted from the IM Examples Logo Generator Script which creates the curvy splash area of the IM Examples Logo

The tricky part of the example is that I convert the Cubic Bezier path string I use, into another path showing the control lines used to generate the bezier curve. This lets me see the curve's control line angles and lengths, making it a lot easier to adjust the results. Only one set of points needs to be adjusted to show both curve and controls, keeping mistakes to a minimum.

   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
[IM Output]

If you look closely at the image you will see that the start and end of the curve has two control lines facing in opposite directions. For a closed continuous path, both start and end control lines should be at the same angle (just in the opposite, mirror direction), and of course the same length. This is important to remember, as it is easy to get this wrong.

All the other points alone the curve only has a single control point/line which points in the opposite direction to the direction the curve is drawn. The 'S' function internally generates first control point/line for the next segment from the data of the previous segment, so as to produce a smooth continuation of the curve.

For more examples of this path function see, SVG: Cubic Bezier Curve Commands.

Manually Generating a Bezier Curve is relatively straight forward without needing any fancy GUI tools.

Interactive Curve Generation is also possible by using some vector graphic editors.

For example Luis Guerra reports that "Inkscape" generated bezier curves can be made accessible using the "Edit -> XML Editor" function then selecting the path or shape you want the control points for.

Do you know of other ways of extracting a bezier curve (giving either two or one control point per point on curve) using a GUI tool. Or perhaps some other technique for generating such curves? Email me! I'd love to hear about it. You will be credited with the technique as others have.

Quadratic Bezier is a simplification of the Cubic Bezier function, when the two control points are merged into a single control point. Again you can start the curve with a 'Q' function, and then use a 'T' function to continue the curve, mirroring the last control point.

  #  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
[IM Output]

I should warn you however that the 'T' continuing function really only works for paths which connect points that are equally spaced.

I do not recommend the use of a Quadratic Bezier Curves in any but the simplest of cases. IE: for simple curved arcs between line segments.

For more examples of this path function see, SVG: Quadratic Bezier Curve Commands.

Warping of the Drawing Surface

On top of these abilities, the drawing surface on which the objects are drawn can be warped in various ways to allow you to do some amazing things.

First you can apply some general drawing surface modifications such as... 'translate', 'rotate', 'scale', 'skewX', 'skewY', and 'affine'.

For example given a 'path' of lines we can "translate" the origin or 0,0 point of the drawing surface to another location.

  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
[IM Output]

Notice that the '0,0' or origin of the drawing area is now centered on the image, though the Y axis remains negative at the top and positive at the bottom of the image.

The "rotate" operation will rotate the drawing surface so anything later drawn on that surface will be drawn rotated. Of course it will rotate around the translated origin, so it is a good idea to use both transformation operators together.

  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
[IM Output]

"scale" will magnify and shrink the drawing surface around the origin.

  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
[IM Output]

One common use of "scale" is to flip the Y axis so that the a positive Y value is upward. Of course the origin should also be moved either to the center, or the lower left corner, to keep things in order.

  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
[IM Output]

And finally, "skewX" and "skewY" shear the image in the X and Y directions. For example, here we use "skewX" to give the vertical Y axis of the image a slant.

  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
[IM Output]

These operators have equivalents outside the MVG "-draw" string, for general use. However these command line versions are operators and are applied immediately to images already existing in memory rather that to a drawn surface only which vector objects have yet to be drawn. For more details see Distorting Images.

Affine Warping of the Drawing Surface

All five of the above canvas transformations can be combined into a general affine matrix operator.
WARNING: The the command line -affine -transform combination will immediately distort
existing images already in memory. The MVG version is a setting
which modifies the drawing canvas onto which objects and images are later
drawn.


  affine  sx, rx, ry, sy, tx, ty   # a transformation & translation matrix

These values are in fact a transformation matrix.

Which form a affine transformation matrix, to map old pixel coordinates
into new coordinates...

                                          | sx  rx  0 |
        | x', y', 0 |  =  | x, y, 1 |  *  | ry  sy  0 |
                                          | tx  ty  1 |

This equates to the mathematical formula...

       ( x', y' ) = ( x * sx + y * ry + tx,   y * sy + x * rx + ty  )

To rotate a point (x, y) by an angle R (in radians), you get
       ( x', y' ) = ( x cos R + y sin R,  -x sin R + y cos R)
Thus, to rotate counterclockwise angle R, you set an affine matrix
             cosR, sinR, -sinR, cosR, 0, 0

After you set your rotation, you can multiply the first two figure by the same
constant to scale the x coord, and multiply the middle two value by some
constant to scale the y coord.  Finally set the last two values for the
placement of the origin, or point of rotation and scaling.

Just set a central origin relative to while objects are drawn...

  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
[IM Output]

Flip image over...

  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
[IM Output]

Rotate by 30 degrees around our origin...

  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
[IM Output]

Under Construction
MVG Save/Recover drawing settings
    push graphic-context
    pop graphic-context


More settings used specifically for MVG handling of SVG format.

    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

Currently some parts of MVG format are incomplete and do nothing. Of course
those same parts in the SVG format are also not yet working, if the internal
SVG conversion is used by IM. This is however being looked at, but is now at a
lower priority.

For examples see Florent Monnier's development site...
  http://www.linux-nantes.fr.eu.org/~fmonnier/OCaml/MVG/

MVG File Handling

Reading MVG Files

As you can see in the above examples the MVG "-draw" arguments can become very long. In fact the conversion of SVG to MVG can produce some extremely long MVG drawing arguments (see below).

However the general command line interface of IM allows you to read any string argument from a file by using a "@filename" argument instead. This is handy as it means you can read in your very long and complex MVG drawing commands from a separate file.

For example, If I put MVG operations into a file called "draw_circles.mvg", I can then draw it like this...

  convert -size 100x60 xc:skyblue  -draw @mvg_circles.mvg  mvg_draw.gif
[IM Output] ==> [IM Output]

Not only that but ImageMagick also understands reading "MVG:" image file format directly allowing you to draw such commands more directly. However unless the MVG file defines a canvas, you may need to specify the initial canvas ("-size" and "-background") for it to draw onto.

  convert -size 100x60  -background limegreen  mvg_circles.mvg  mvg_file.gif
[IM Output] ==> [IM Output]

You can move the initial canvas settings into the MVG image by adding a 'viewbox' to the MVG file, with appropriate background color fill draws. That completes the MVG image file as a complete image definition.

  convert    mvg_circles2.mvg    mvg_image.gif
[IM Output] ==> [IM Output]

There is currently only one way of reading a external MVG file from inside a MVG argument string, and that is using a 'image' drawing primitive. Unfortunately this converts the MVG include into a image BEFORE overlaying that image onto the drawing surface. In other words it is not drawn as a vector image, but a raster image.

In other words there is currently no MVG 'include' function.
:-(

Under Construction

You can generate the low level draw operations of IM, using the "+render" to record them. 

When you then give a "-render" setting/operator, IM will immediately draw those saved operations.

Strangely just using a "MVG" format output file also seems to do this... convert ... -draw '....' draw_commands.mvg NOTE: if you draw a curve while outputting a MVG format file, the file lists the curve as a series of short line segments, rather than the original curve. You can of course go the whole way and use the more universal SVG format. See "SVG format handling" below.

Stroke, StrokeWidth and Fill Interaction

The "-stroke" and "-strokewidth" options are used when drawing an outline around a font's edge.

These options commonly used with "-fill" to make text more interesting, for very little effort.

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

As you can see above, the default setting of "-strokewidth" is 1. However the outline stroke of draw is by default turned off as if you used, "-stroke none", which in effect draws the transparent color, resulting in no outline stroke.

Here we generated a image of the same font drawn as normal, and 5 different "-strokewidth" settings, so you can see their effect.

    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
[IM Output]

Note from the the above examples that setting a "-strokewidth" of '0' is NOT the same as setting the "-stroke" color to 'none' (the default). The former makes a very very thin stroke outline, while the latter effectively turns it off. In both cases the stroke is still drawn.

Here is an example of using an extreme stroke width.

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

Note that "-strokewidth" expands both lines inward and outward. Here is the same example but with the font re-drawn, without the stroke outline, to remove the inside part of the very thick stroke.

   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
[IM Output]

For more examples of using stroke see Compound Font Effects. Have a special look at the "Balloon Effect".


Drawing Lines, Line Width and Stroke

The default line drawing in IM has few weird behaviours, which are worth knowing about.

Here is the default line draw...

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

You can set the color of the line with a "-fill" option.

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

Also you can make a line slightly thicker by setting the "-stroke" color.

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

But what happened to the white color we specified with the "-fill" option?

This is the tricky aspect of drawing lines in ImageMagick. What the program does is actually consider the line as a filled object about 1 pixel wide. This is natural, as typically multiple lines are generally used to sweep out an area that is to be filled.

So just as when we used stroke with fonts in the previous section, IM draws the line (or object) using the fill color, then draws around it with the stroke color. The result is that the above stroke color line is now slightly thicker, with the fill color completely hidden underneath. If you make the stroke color semi-transparent you can make that fill color visible again.

To summarize, lines will appear to be drawn with the "-fill" color, but that option is of no consequence once the "-stroke" color has been defined as something other than the default "none" or "transparent" colors.

The option "-linewidth" is really only an alias for "-strokewidth".

For example, you would probably think that this command would produce a very thick line. It does, but as the "-stroke" color is invisible you can't see it. You only see the inside 'fill' of the one pixel wide area of the line.

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

But if the stroke color is also defined, you will get the thick line requested...

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

If the "-strokewidth" setting is set to one, the above line will be completely covered.

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

Of course when you are armed with this knowledge, you can use it to be creative, just as you can with font drawing.

  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
[IM Output]

Here I used the thinnest "-strokewidth" setting of '0', just as I did for the fonts above.

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

This produces the very strange result of a dotted line, consisting of black dots and grey segments. This is the result of a weird "color beat frequency" between the stroke, fill and background colors.

Here is an enlarged view of the line...

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

The "color beat frequency" effect is not unlike that of a "sound beat" you get when you have two guitars which are very slightly out of tune. In this case you get a black dot where the stroke color completely overrides the underlying fill color and you get a grey dot where the stroke color mixes with BOTH the fill and the background colors.

The color mixing is a natural consequence of the anti-aliasing processes which IM uses to try to improve the look of lines and other draw objects. For more information see my Anti-Aliasing in IM discussion and examples page.

Note that this effect only appears on slanted lines, not pure horizontal or vertical lines, where aliasing has no effect and thus no "color beat frequency" effects.

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

Here I used different underlying fill colors on the enlarged view, so you can see how the color changes the resulting beat.

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

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

  convert -size 25x10 xc:lightblue \
          -fill black -stroke black -strokewidth 0 -draw "line 2,8 22,1" \
          -scale 400%    line_stroke_0_black.jpg
[IM Output]
Lets compare that to a stroke of none...

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

As you can see, when drawing very thin lines, you can reduce that 'beat' by either using the same fill and stroke colors, OR setting one of the colors to none to turn it off. While the later is the best idea, the former may be more practical for your specific programming needs.

Note the fill line thickness is '0'. But the the stroke line can have a larger thickness, and is a floating point value! A 1/2 pixel wide line is perfectly valid.


Drawing Symbols as a List of Points

Sometimes you have a set of points on an image where you want to draw reference symbols, like crosses, circles, etc... Unfortunately at this time IM does not have commands to draw such symbols easily, but with a little bit of extra work you can draw such symbols.

The trick is to generate the MVG drawing commands using a shell script, or whatever API you are using, so as to transform the given a set of points into the appropriate drawing commands.

For example here I convert a line of points, into a 'plus' at each of those points...

  # 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
[IM Output]

The above uses "tr" to separate each point into one point per line, then uses "awk" to do all the mathematical calculations needed to draw the 'plus' over the given point. As you can see it can become quite complex.

However you may not be able to rely on 'awk' or 'perl' or even 'bc' to do floating point calculations needed to draw a 'symbol'. On the other hand you know that the "convert" program is available, so you can use it to do the required floating point calculations. For example here I use it to calculate a point on the circumference of the circle, for this 'point symbol'.

  # 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
[IM Output]

Now the draw strings you generate can get fairly long, and could start to cause problems. So rather than converting the points into long strings which we then pass to IM on the command line, you can pipe draw commands instead.

I also this time use a SVG Path instead of a Draw Primitive, this time generating a triangle around each point.


  # 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
[IM Output]

The SVG Path actually makes this easier, by allowing relative pixel moves, allowing you to design the symbol so it only requires a single initial absolute move 'M' before giving the sequence to draw the symbol. Because of this you actually do not need any floating point calculations at all.

The relative move SVG Path item 'm' is broken before IM v6.4.3-5. If your IM is older than this, the above (and next) examples may not draw anything. You can fix this for older versions by replacing the relative moves 'm' in the above with an appropriate sequence of relative lines, 'l'.

Now you can take this even one step further, feed a fully formed MVG file, complete with draw canvas specification, directly into IM as a pipeline of drawing commands. This time lets do a 'cross' which is similar to the first 'plus' example above which needed a lot of calculations.

  # 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
[IM Output]

This uses the special shell programming technique where anything and everything that is 'echoed' within the shell parenthesis will be fed into the final "convert" command as a MVG file. The first 'echo' defines and fills the drawing canvas for the image, while the 'while' loop converts each 'point' giving into a circle of the given radius.

The advantage of this method is that you don't get any string limitation that you may get using the other two methods.

Other symbols that you could generate include boxes, diamonds, error-bars, etc...

Also see 'Drawing Circles below, for other circle methods, including a no-calculate relative 'path' circle draw.

It would be nice if the MVG could be expanded to allow you to more easily 'draw' simple symbols at a list of coordinates.


Drawing Circles

The draw options provide you with a number of ways to do something very basic... Drawing Circles. You can, for example, draw a circle though any point on its circumference. Thus you will need to calculate a center point and a second point that is the radius (say 25 pixels) away from the first point.

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

But you can also specify an ellipse with a fixed radius set of arguments.

  convert -size 100x60 xc:  -stroke SeaGreen  -fill PaleGreen  -strokewidth 2 \
          -draw 'ellipse 50,30 25,25 0,360'    circle_ellipse.gif
[IM Output]

You can also generate a circle by drawing a very very short line with 'stroke-linecap round'. The stroke width then sets the circles diameter. NOTE the line must have some length (no matter how small) or draw will draw nothing.

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

This technique, unfortunately can not outline the generated circle, but for covering large areas, large stroke widths can be useful. See Some simple examples below.

The last method makes use of the SVG Path drawing method so that the circle can be drawn without needing to calculate any extra coordinates.

  convert -size 100x60 xc:  -stroke Navy  -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 Output]

Only the initial absolute move 'M' is needed to define the center, the '25' and '50' in the rest of the path components that follow define the circle radius and diameter relative to this center.

The relative move SVG Path item 'm' is broken before IM v6.4.3-5. If your IM is older than this, the circle may only appear as a single pixel. You can fix this for older versions by replacing the 'm' in the above with a 'l'.

Fred Weinhaus added the following bezier circle method. It is very close to a real circle (though not exact), and requires a floating point calculation.

  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
[IM Output]

If drawing an exact circle is not important, you can use this 4 Bezier segment SVG path, that only uses the X and Y bounds of the circle for its calculation.

  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
[IM Output]

If you like one that is drawn completely relative to a center starting point, you can use this technique. Only the radius value is used, making it simple to generate, using only string functions in an 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
[IM Output]

Can you think of other ways to draw circles.


Drawing Special Characters in the Text String

To Quote or Backslash?

One of the biggest problem people have with -draw is the drawing of characters that also have special significance to UNIX shells and the DOS command line or even other languages like C, Perl, PHP, R, or Visual Basic.

The biggest culprit in this regard are the two types of quoting characters, as well as variable substitutions characters like dollars '$' and the shell and ImageMagick escape character, backslash '\'.

Basically as the MVG argument to "-draw" needs to be quoted, and the 'text' string argument within also may need some extra quoting as well.

To solve this, users typically use two different quote characters, one for the shell and a different one for the MVG text string.
-draw '... text 0,0 "string" ...'

or they would swap the quotes, and use...

  -draw "... text 0,0 'string' ..."
This solves most problems, but some characters still present difficulties, and the solutions depend on exactly what set of quotes you use.

Here are the four cases of quoting... As you can see "-draw" arguments from the command line have to deal both with the command line shell as well as backslash and quote escaping within the MVG text string. The results can be confusing and tricky. Just remember that the shell treats the two types of quotes differently, while the MVG text string does not.

Of course in complex scripts, the better way may be to avoid the shell and any scripting problems entirely. You can do this by reading the "-draw" arguments from a MVG draw file.

-draw @drawfile.mvg
Of course you will still need to backslash whatever quote character you decide to use around your draw text string, if you want to include that character as well as backslash any backslashes within the text, but it is a lot simpler than trying to dealing with a shell's own quote and backslash interference at the same time.


  convert -size 500x50 xc:lightblue  -font Candice -pointsize 36 \
          -gravity center     -draw @text_quotes.mvg      text_quotes.gif
[IM Output]
==> [IM Output]
The first image is from one of the "MVG" text files I used. It contains no shell escapes or quoting. As such only the MVG quoting and escapes are present.

Note that in the above if I had used single quotes for the MVG text string, the only change is that I would need to backslash the single quote characters rather than the double quote characters in the string.

About Percent Characters

Just one final point about special 'escape' characters in the "
-draw text" operator. Percent characters '%' should draw 'as is'. You should not need to do anything special to draw them. If they don't draw 'as is', then you have an old version of IM and should upgrade ASAP.

Up until IM version 6.2.4, the '%' character was used as an escape character to include extra image information in the drawn text string. This is no longer the case as such escapes were confusing and incorrect when SVG images also tried to draw percent characters.

This use of percent 'escapes' (as well as '\n' newline escapes) was deemed incompatible with the "-draw" operator, and MVG format's intended use for handling SVG image formats. As such from IM version 6.2.4 onward, % escapes do not work, and backslashes only escape itself and the surround quotes.

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

For more details of the 'percent bug', and ways to avoid it when using "-draw" in older ImageMagick's, see the Drawing a Percent Bug page.

Annotate instead of Draw

The better way of avoiding these types of problems is to use "
-annotate" rather than draw for text drawing. This operator is a wrapper around the draw operator and allows the use of all the capabilities of draw, but in a simpler form.

Basically this operator only need one set of quoting (for the shell). This makes dealing with special characters a much much simpler.

Unfortunately while you no longer need to escape quotes for IM, you now have to deal with '@' file reads, newline escapes '\n', and the percent information includes.

For example, using single quotes...

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

and for double quotes...

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

However all annotation quotes and escapes are completely ignored if you use the at '@' escape to read the string from a file.

For example here we include information on an images width and height!

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

But here the escapes are ignored when read from a file.

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

For more information see Annotate Text Drawing Operator, and especially Annotate Escape Characters.


MVG Alpha Composition

Under Construction
I have not seen any use of Alpha composition (other than 'painters' algorithm
which is basically a 'over' alpha composition) for the drawing of objects.

However that is not to say it can not be done.

If you like to compose your rectangle, ellipse, circle, or whatever with a
different alpha composition (such as 'DstOver' which is an Under-like
composition),  then draw your figure on a blank transparent canvas the same
size as the original and compose it onto your image.

However as SVG allows you to use alpha composition to draw text and other
items onto images, I would imagine that it will be a future addition.

Stay Tuned!

IM and SVG handling

Handling the actual SVG image format is a very complex business. The engine needed to handle all its aspects, as defined by the SVG -- Scalable Vector Graphics document requires a lot of effort, and time.

Therefore, ImageMagick provides two methods in the handling of SVG format images. The first is to use a open source RSVG library, to convert the SVG format into a raster image that IM has no problems handling.

The second method is for IM to try to convert SVG into MVG, so that it can draw the image itself. MVG was (and is) being designed for this purpose. The method is known as the internal MSVG conversion.

You can force the use of the internal MSVG converter by reading the SVG image using using the special input format "MSVG:" (added IM v6.3.4). However the only way to add RSVG library support is to compile IM with the 'development' support for the RSVG library installed on your machine.

To find out what your IM will do use...

  convert -list format | grep SVG
[IM Text]

As you can see by the "RSVG" in parenthesis, my own IM will use the RSVG library that was provided by the system on my personal workstation. IM is using this library because it was present when I last compiled IM, which is almost daily. See the IM version and compile date used for these examples, at the bottom of the page.

For example "diagonal.svg" is a very small SVG image (from user penciledin), which creates a rectangle with a simple diagonal gradient.

  convert diagonal.svg  diagonal_rsvg.gif
[IM Output]

Perfect. A proper diagonal gradient is produced.

However if you render this using the internal MSVG (the default if the RSVG was not present when when IM was compiled)...

  convert msvg:diagonal.svg  diagonal_msvg.gif
[IM Output]

As you can see the internal MSVG conversion fails, returning a vertical gradient rather than a diagonal one. This is a known problem and is being fixed as time allows.

You can also see the actual MVG commands IM generates by inspecting the "diagonal.svg (txt)" source.

  convert msvg:diagonal.svg diagonal.mvg
[IM Text]

Things the current internal MSVG is known to fail with include... However most basically drawing actions are handled.

Remember the MVG language can actually handle things that SVG can not, including the use of gravity for positioning image and text. Gravity is not part of the the SVG specification, though it is an integral part of IM text and font handling.

Also remember that MVG does not have the same container mechanism that SVG has. The internal MSVG converter replaces the XML containers with the pushing and popping of graphic contexts (see the MVG output above), which has the same effect.

SVG settings

As the SVG image format is a vector format (See A word about Vector Image formats) the image does not have a default 'size'. Instead it is 'drawn' or 'rendered' at a particular "-density" just like postscript (default density is 72 dpi).

Also if the SVG does not 'paint' the background, you can specify the background color to use by using the "-background" setting.

For example here is another SVG image which has been 'rendered' using 3 different densities, and to 3 different backgrounds, including a transparent background.

  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
[IM Output] [IM Output] [IM Output]

Note that I used a PNG format image for the larger transparent background version of the above example. This produces a cleaner image than a GIF image format would produce due to semi-transparent edge pixels. PNG is always recommended when transparency is involved in the final image.

I have found that some SVG images will not scale. That is they are defining there internal image components in terms of pixels, rather than real-world lengths such as 'points', 'inches' or 'millimeters'. As a consequence while the "-density" setting may change the overall image size of the result, the image itself will not change size to match. Such SVG images are however quite rare.


SVG Output Handling

As of IM v6.4.2, IM can convert ANY bitmap image into an SVG vector graphic! The conversion is not always successful, but larger and/or simpler images (like a bitmap mask) will convert very well.

For example here I convert a horrible bitmap shape into a SVG image, then convert it back again, so as to smooth the bitmap into a proper anti-aliased shape.

  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
[IM Output] ==>
[IM Text]
==> [IM Output]

For this to work however the 'development' "AutoTrace" library must be installed, and IM configured with a "--with-autotrace" switch.

There is also a "autotrace:' input delegate command to do exactly what the last example did when reading in a bitmap image, so as to smooth the resulting image in memory. This does not require the delegate library, only the "autotrace" command to be present on the system.

  convert autotrace:A.gif  A_traced.png
[IM Output]

Of course this will NOT get you the SVG output from the "autotrace" command, just filter the input image through SVG to smooth it.

See Raster to Vector Edging for a example of using the SVG generated from "autotrace", as a image processing technique.


Non-IM Vector Graphic Editors

ImageMagick is a pixel array processor, It will generally not save vector
images ('MVG' is the only exception to this), only read them and convert them
to pixel arrays.

The same is true of other pixel image editors, such as Gimp, Photoshop,
and so on.


For editing and handling vector based images use programs such as

   Sodipodi -- SVG based Vector Graphics Editor
   Xfig     -- Simple, but very good, Vector Object Editor
               (Great for signs, maps, and arranging photos on a page)
   Dia
   AutoTrace   convert a shape in a bitmap array to vector outlines
   Sketch      python based vector editor with curved text.

This is of course not the be-all-end-all as Word for Windows, OpenOffice
and other word processors also have various simple, but difficult to
use, object editors.

For converting a vector graphic format to a different vector format, do
not use ImageMagick.  ImageMagick is, and always will be, essentially
a raster image or bitmap graphics converter and manipulator.

For converting from vector to vector take a look at these programs...

   pstoedit   Translate PS and PDF to other vector formats (like SVG)
   Convertps  commercial version of a PS to a vector language convertor
   ps2svg     PostScript to SVG - based on GhostScript


Some simple examples

A background for a sign.

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

Start of a Clown face. Can you complete it?

  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,41' clown.gif
[IM Output]


Created: 24 March 2004
Updated: 22 August 2008
Author: Anthony Thyssen, <A.Thyssen@griffith.edu.au>
Examples Generated with: [version image]
URL: http://www.imagemagick.org/Usage/draw/

a