Scripted drawing with ImageMagick
Silhouette
ImageMagick can do more than just edit existing images. The free software can even be scripted to create simple drawings.
Although you would normally use a bona fide graphics program for drawing and painting, there are definitely situations in which you need to draw regular shapes in an image repeatedly at fixed intervals – as shown here, for example, when creating the silhouette of an imaginary city (Figure 1). This does not require an expensive graphics program with a sophisticated macro language. Using the free and open source ImageMagick software package at the command line is more than up to this task.
To compose more extensive images, you will need the support of a scripting language such as Bash, which uses loops and other control structures to repeatedly insert image content into the graphic. ImageMagick can be found in the package sources of most Linux distributions, but it can also be downloaded for installation from the download section of the project page [1].
Painting by Commands
After completing the install, type
magick logo: logo.gif
at the command line. ImageMagick will create the logo.gif
file in the current directory. It shows the magician seen in the upper right corner of Figure 1. You can easily check this by opening the file in a suitable image viewer. Image names ending with a colon are internal test images in ImageMagick. You can create more test images with the rose:
and wizard:
[2] options.
To demonstrate that you can actually draw at the command line with ImageMagick, see the command in Listing 1.
Listing 1
Rectangle
$ magick -size 500x300 \ xc:skyblue -fill red \ -draw 'roundrectangle \ 100,50,400,250,80,60' \ image.png
What this does is to first set the image size with the -size
parameter. Then xc:skyblue
paints the background of the image sky blue. Historically, xc:
stands for "X Constant Image," but today it stands for canvas. The instruction expects a color name from the X Window System palette, which ImageMagick adopted [3].
You can also specify a gradient, for example, by typing gradient:blue
instead of xc:skyblue
. The built-in gradient image generator would then create a blue gradient background image for you. For an idea of the possibilities, check out the ImageMagick documentation. The manual goes into the details of plasma canvases [4] and gradients [5], for example.
The -fill
parameter is followed by the definition of the drawing color for the subsequent drawing command, which is introduced by -draw
and quoted. The last parameter is the name of the image file to be created.
The resulting image is a red rectangle, rounded at the corners, on a light blue background. You create more complex drawings by stringing together several drawing commands (Listing 2, lines 1 to 3).
Listing 2
Multiple Elements
01 $ magick -size 500x300 xc:skyblue -fill red -draw 'roundrectangle 100,150,400,225,55,10' \ 02 -draw 'roundrectangle 200,100,350,180,50,10' -fill black -draw 'circle 150,225 180,225' \ 03 -draw 'circle 350,225 380,225' auto.png 04 $ magick auto.png -fill red -draw 'polygon 175,150 200,110 200,150' auto-bevel.png 05 $ mogrify -fill red -draw 'polygon 175,150 200,110 200,150' auto.png
Three-Box Car
The result of the first call from Listing 2 is shown in Figure 2: a very simplified image of a notchback based on the three-box principle. By passing in the drawing command from line 4 of Listing 2, you can improve the boxy shape a little. The command angles the passenger compartment a bit at the front.
The auto.png
file I just created provides the basis for subsequent beveling of the passenger compartment. The command writes the results to the auto-bevel.png
file. If you want to avoid constantly recreating files, use the command from the last line of Listing 2 instead. This modifies the auto.png
file directly.
Although both methods have advantages for your first steps in getting to know ImageMagick, they have one decisive disadvantage. They are both comparatively slow. As you can imagine, drawing an entire skyscraper this way is complicated. You would have to string together several draw
commands, and a separate call would be required for each individual window. And you would have to manually calculate the positions of the windows up front.
Faster with Scripts
The slowness in drawing a skyscraper can remedied by using a script that determines the image size based on the information it receives about the desired number of floors and windows per floor. It also defines a color gradient for the background and saves the image. It then calculates all the drawing instructions for the actual building and the windows and doors and collects them in an XML-formatted Magick Scripting Language (MSL) file.
Finally, the script uses a conjure
statement to process the mess of drawing commands. This greatly improves speed over incrementally developing the image using individual mogrify
statements. Listing 3 shows an MSL file for a small two-story house with a foundation, five windows, and a front door.
Listing 3
miniHouse.msl
<?xml version="1.0" encoding="UTF-8"?> <image> <read filename='miniHouse.png' /> <draw fill='sandybrown' primitive='rectangle 250, 1940, 810, 2500' /> <draw fill='snow4' primitive='rectangle 0, 2500, 1060, 3000' /> <draw fill='yellow' primitive='rectangle 270, 1970, 370, 2090' /> <draw fill='yellow' primitive='rectangle 690, 1970, 790, 2090' /> <draw fill='yellow' primitive='rectangle 440, 1970, 620, 2090' /> <draw fill='yellow' primitive='rectangle 270, 2250, 370, 2370' /> <draw fill='yellow' primitive='rectangle 690, 2250, 790, 2370' /> <draw fill='yellow' primitive='rectangle 440, 2250, 620, 2500' /> <write filename='miniHouse.png' /> </image>
You call the buildHouse
shell script, on which this workshop is based, using the syntax in Listing 4. Its contents are taken unchanged from Listing 5. For the sake of simplicity, the script does not perform an extensive check of the parameters you pass in. In addition, only positioning parameters are used. The call sequence in the command line determines the assignment in the script for this. If you want to improve this script, you need to look into the shell's error checking options and also consider using getopts
in the script.
Listing 4
Sample Syntax
$ ./buildhouse <File> <Floors> <WindowsPerFloor>
Listing 5
buildHouse
#/bin/bash if [ -z "$1" ]; then echo "Please specify at least the name of the output file without a file extension as the parameters, optionally also the number of floors and windows per floor." exit fi rows=5 columns=10 ox=250 # X-offset of the house oy=2500 # Y-offset of the house if [ $# -ge 3 ]; then rows=`expr $2` columns=`expr $3` if [ $# -ge 4 ]; then ox=`expr $4` fi fi rh=250 # room height fh=120 # window height fb=100 # window width dh=30 # ceiling height fa=20 # window distance ma=300 # center distance/staircase bay width mslfile="$1.msl" housewidth=`expr $columns \* \( $fa + $fb \) + $fa + $ma` househeight=`expr $rows \* \( $rh + $dh \)` t="$ox, `expr $oy - $househeight`, `expr $housewidth + $ox`, $oy" center=`expr $housewidth / 2 + $ox` mahalb=`expr $ma / 2` center-left=`expr $center - $mahalb + \( 3 \* $fa \)` center-right=`expr $center + $mahalb - \( 3 \* $fa \)` magick -size"`expr $ox + $housewidth + $ox`"x3000 gradient:#0000ff-#ffffff -draw "rectangle $t" $1.png str="0, 2500, `expr $ox + $housewidth + $ox`, 3000" echo '<?xml version="1.0" encoding="UTF-8"?>' > $mslfile echo '<image>' >> $mslfile inputfile="<read filename=\"$1.png\" />" echo $inputfile >> $mslfile echo " <draw fill='sandybrown' primitive='rectangle $t' />" >> $mslfile echo " <draw fill='snow4' primitive='rectangle $str' />" >> $mslfile top=`expr $oy - $househeight + $dh` left=$ox for ((i=0; i < lines; i++)) ; do links=`expr $links + $fa` for ((j=0; j < columns; j++)) ; do varInt=`expr $columns / 2` if [ $j -eq $varInt ]; then links=`expr $links + $ma` fi t="$left, $up, `expr $left + $fb`, `expr $up + $fh`" echo " <draw fill='yellow' primitive='rectangle $t' />" >> $mslfile links=`expr $links + $fb + $fa` done if [ $i -ne `expr $lines - 1` ]; then tm="$center-left, $top, $center-right, `expr $top + $fh`" echo " <draw fill='yellow' primitive='rectangle $tm' />" >> $mslfile else tm="$center-left, $top, $center-right, `expr $top + $rh`" echo " <draw fill='yellow' primitive='rectangle $tm' />" >> $mslfile fi left=$ox top=`expr $top + $rh + $dh` done outputfile="<write filename=\"$1.png\" />" echo $outputfile >> $mslfile echo '</image>' >> $mslfile conjure msl:$mslfile
When calling the script, you need to specify the file name without a file extension. The .msl
and .png
extensions are added automatically by the code. If you leave the option for the number of floors and the number of windows per floor empty, the script will use default values. The file name, on the other hand, must always be specified, otherwise the program will abort. You always need to append the number of floors and windows per floor together, otherwise the script will not evaluate the parameters completely and will replace both with default values.
Once you have made all the entries, you can optionally specify a fourth parameter for correcting the boundary distance to the neighboring property if you want to recreate the sample settlement from Figure 1 as shown in Listing 6. Table 1 explains the meaning of the assembly
and composite
tools, among other things.
Table 1
Command Line Tools in the ImageMagick package
Tool Name |
Function |
|
A standard tool from the ImageMagick package, it can convert file formats and scale, blur, crop, denoise, dither, rotate, flip images, and much more. |
|
Outputs a description of the format and characteristics of one or more graphics files. |
|
Offers the same functions as |
|
Overlaps two images. |
|
Assembles multiple images into one. |
|
Displays the differences between two graphs (as a report of a mathematical analysis and visually). |
|
Copies single or multiple pixel components of an image to another format (mainly intended for very large image files). |
|
Displays an image or image sequence via an X server. |
|
Creates screenshots in X11. The function optionally saves the entire screen area, the area of a window, or a defined rectangle. |
|
Interprets scripts in the MSL and executes them. |
Listing 6
Building a Skyline
$ ./buildHouse house1 8 8 $ ./buildHouse house2 4 10 0 $ ./buildHouse house3 5 10 0 $ ./buildHouse house4 1 8 250 $ magick composite house1.png house2.png house3.png house4.png -geometry +0 -tile x1 street.png $ magick composite logo: street.png -gravity NorthEast street-magick.png
Buy this article as PDF
(incl. VAT)