Linux tip

Creating a pixel ruler from the command line

How to draw lines and text on images with Bash scripting, shell arithmetic, and ImageMagick

Content series: This content is part # of # in the series: Linux tip Stay tuned for additional content in this series. This content is part of the series: Linux tip Stay tuned for additional content in this series.

Sometimes I need to draw a few lines or some text on an image or a blank canvas. Just recently I needed to include a simple image of a pixel ruler in a developerWorks article. I wanted the image to be 572 pixels wide, to match the current recommended maximum for developerWorks article images as described in "Illustrating your article or tutorial for developerWorks." Lots of on-screen pixel rulers are available for Linux and for Windows (see Related topics), but I just wanted a simple GIF, JPEG or PNG image. This tip shows you how to use Bash scripting, shell arithmetic, and ImageMagick to create a pixel ruler.

Make a canvas

The first thing any artist needs is a canvas, so let's create one using the XC pseudo image type in ImageMagick. We also need a color, either one of the many named colors in ImageMagick, or a custom color. (See Related topics for a link to the ImageMagick documentation, where you can find a full list of pseudo image types as well as color names.) Listing 1 shows how to make a light blue 572x100 canvas using the convert command. (The convert command is customarily used to convert to a different image format or otherwise alter an image.)

Listing 1. Creating a canvas

convert -size 572x100 xc:lightblue ruler1.gif

Figure 1 shows our new canvas.

Figure 1. A light blue 572x100 pixel canvas for our ruler

Add a few lines

Now that we have a canvas, let's draw marks to show the various points along the ruler. 72 ppi (pixels per inch) is standard for Web-based graphics, so let's put our first mark at 72 pixels and make it extend 30 pixels from the bottom of the image. Let's also make the fill color for the line black and specify the two end points of the line.

Listing 2. Adding a line to the canvas

convert -fill black -draw "line 72,70 72,100" ruler1.gif ruler2.gif

Figure 2 shows our new image with a single line drawn on it.

Figure 2. The canvas with a single vertical line 72 pixels from the left

Doing this individually for each line we want on the ruler isn't too appealing. So let's use the seq command to generate a list of horizontal offsets 72 pixels apart and a for loop to draw our main lines on the ruler as shown in Listing 3.

Listing 3. Adding lines to the canvas every 72 pixels

convert -fill black -draw "$(for n in $(seq 0 72 572) ;\ do echo line $n,70 $n,100 ; done)" ruler1.gif ruler3.gif

Notice that we use two command substitutions to generate the individual line specifications for the draw operation. We also put a mark at 0, the left edge. Figure 3 is now starting to look more like a ruler.

Figure 3. The canvas with vertical lines every 72 pixels from the left

Scripting more lines

At this point, our command line is getting a little complex, and we still have only the major marks on our ruler. Time to start scripting. In Listing 4, we use shell arithmetic and for loops to place a 20-pixel mark half way between the marks we already have, and to place 10-pixel marks every 6 pixels between the longer marks. We'll call the script buildruler.sh and put it in our working directory for the purposes of this article.

Listing 4. Adding a full set of marks to the ruler

#!/bin/bash # Take user parameters or set defaults rulername="$1" rulerlength="$2" rulername="${rulername:=ruler.gif}" rulerlength="${rulerlength:=572}" drawstring="" #Build the line definitions for the ruler marks for x1 in `seq 0 72 $rulerlength`; do drawstring="$drawstring line $x1,70 $x1,100" for x2 in 0 36; do (( offset = $x1 + $x2 )) drawstring="$drawstring line $offset,80 $offset,100" for x3 in `seq 6 6 30`; do (( offset2 = $offset + $x3 )) drawstring="$drawstring line $offset2,90 $offset2,100" done done done #Create the ruler convert -size "${rulerlength}x100" xc:lightblue -fill black \ -draw "$drawstring" "$rulername"

Notice that we added a couple of parameters to allow a user to change the name of the ruler and to specify the length. Our ruler now looks like Figure 4. We used the command ./buildruler.sh ruler4.gif to generate this ruler.

Figure 4. Our ruler with a full set of marks 6 pixels apart

Adding text

Quoting One of the tricky things with scripting is knowing when quotes are needed and when they are not, and how to pass quoted strings as parameters. Pay particular attention to the quoting used in these scripts.

Now let's label the larger marks on our ruler with the numbers 0, 72, 144, and so on. We need to tell ImageMagick what font to use and what size and color it should be. Obviously, the 0 should be at the left edge of the ruler, but if we place the two- and three-digit numbers at the 72-pixel points, they will appear unbalanced. To correct this, we'll shift them slightly to the left, to make them appear more centered over the mark. Our enlarged script is shown in Listing 5.

Listing 5. Adding labels to the ruler

#!/bin/bash # Take user parameters or set defaults rulername="$1" rulerlength="$2" rulername="${rulername:=ruler.gif}" rulerlength="${rulerlength:=572}" drawstring="" #Build the line definitions for the ruler marks for x1 in `seq 0 72 $rulerlength`; do drawstring="$drawstring line $x1,70 $x1,100" for x2 in 0 36; do (( offset = $x1 + $x2 )) drawstring="$drawstring line $offset,80 $offset,100" for x3 in `seq 6 6 30`; do (( offset2 = $offset + $x3 )) drawstring="$drawstring line $offset2,90 $offset2,100" done done done #Add the labels labelfont="-fill black -font helvetica -pointsize 24 -draw" labelstring="text 0,60 \"0\" " for x1 in 72; do (( offset = $x1 - 12 )) labelstring="$labelstring text $offset,60 \"$x1\" " done for x1 in `seq 144 72 $rulerlength`; do (( offset = $x1 - 18 )) labelstring="$labelstring text $offset,60 \"$x1\" " done #Create the ruler convert -size "${rulerlength}x100" xc:lightblue -fill black \ -draw "$drawstring" $labelfont "$labelstring" "$rulername"

Now our ruler looks like Figure 5.

Figure 5. Our ruler with labels a full set of marks

Positioning text

So how did we know how much to offset the text or how far down from the top to place it? The short answer is that I experimented and guessed! Not an ideal solution.

To do a better job on placing text, we need to know how big the rectangle containing the text will be. But that depends on the font and whether the font is proportional and other things that aren't so easy to calculate. Fortunately, in the same way that we can create a canvas with ImageMagick, we can also create a label. We'll create a title of Pixel Ruler and make it a 36-point font. The label is created as an image, and we can use the identify command to determine its size, as shown in Listing 6.

Listing 6. Creating a title and measuring its size

$ convert -fill NavyBlue -background Lavender -font helvetica -pointsize 36 \ label:"Pixel Ruler" label.gif $ identify "label.gif" label.gif GIF 175x36 175x36+0+0 8-bit PseudoClass 256c 1.98kb

We've made the text navy blue and added a lavender background color to help the image stand out from this page background, but you get the same size image with or without the background color. The resulting label image is shown in Figure 6.

Figure 6. The label as an image

Even better than writing a file, we can use the ImageMagick INFO class to determine even more information about the text, including where the baseline is. We'll write the text onto a large enough canvas and then trim the canvas to the edge of the text as shown in Listing 7.

Listing 7. Using the INFO image class to determine font information

$ convert -size 572x100 xc:lightblue -font helvetica -pointsize 36 \ -fill black -undercolor lavender -annotate +40+50 'Pixel Ruler' -trim info: xc:lightblue XC 174x36 572x100+40+23 16-bit DirectClass

Annotated text starts at a point whose y coordinate represents the baseline of the text. The above output shows that the text fits in a box that is 174x36 pixels in size. The 1-pixel discrepancy between this and our earlier result is not significant. The top of the box is positioned 23 pixels below the top of our original canvas. Since the baseline was 50 pixels below the top of the original canvas, this means the baseline is actually 27 pixels (50 minus 23) below the top of the text, leaving 9 pixels for descenders. Figure 7 illustrates the relationship using an image placed on the untrimmed canvas.

Figure 7. The label metrics

So now that we have the label dimensions, let's place the baseline 40 pixels from the top of the image and center it horizontally. Our final script is shown in Listing 8 and is also available for download.

Listing 8. Our final script

#!/bin/bash # Take user parameters or set defaults rulername="$1" rulerlength="$2" rulername="${rulername:=ruler.gif}" rulerlength="${rulerlength:=572}" drawstring="" #Build the line definitions for the ruler marks for x1 in `seq 0 72 $rulerlength`; do drawstring="$drawstring line $x1,70 $x1,100" for x2 in 0 36; do (( offset = $x1 + $x2 )) drawstring="$drawstring line $offset,80 $offset,100" for x3 in `seq 6 6 30`; do (( offset2 = $offset + $x3 )) drawstring="$drawstring line $offset2,90 $offset2,100" done done done #Add the labels labelfont="-fill black -font helvetica -pointsize 24 -draw" labelstring="text 0,60 '0' " for x3 in 72; do offset3=$(($x3 - 12 )) labelstring="$labelstring text $offset3,60 '$x3' " done for x4 in `seq 144 72 $rulerlength`; do offset4=$(( $x4 - 18 )) labelstring="$labelstring text $offset4,60 '$x4' " done #Add a title titledimension=$(convert -size 572x100 xc:lightblue -font helvetica \ -pointsize 36 -fill black -undercolor lavender\ -annotate +40+50 'Pixel Ruler' -trim info: | awk ' {print $3 } ') titlewidth=${titledimension%x*} titlefont="-fill NavyBlue -font helvetica -pointsize 36" titlepos=$(( (($rulerlength - $titlewidth)) / 2 )) titletext="text $titlepos,30 'Pixel Ruler' " #Create the ruler convert -size "${rulerlength}x100" xc:lightblue \ -fill black -draw "$drawstring" $labelfont "$labelstring" \ $titlefont -draw "$titletext" "$rulername"

The final ruler is shown in Figure 8.

Figure 8. The final ruler, complete with title

Not too bad for someone with no artistic skills!

A semi-practical example

When images are indented—as in lists, for example—the maximum width allowed on developerWorks is reduced accordingly. So you can use these handy rulers as a check for any images you submit with an article.

This is a short ruler in an unordered list that was created with the command ./buildruler.sh ruler9.gif 400 . Figure 9. A 400-pixel ruler And this is an even shorter ruler that is further indented. It was created with the command ./buildruler.sh ruler10.gif 300 . Figure 10. A 300-pixel ruler

.

Conclusion

In this short exercise, you've seen some basic techniques for scripting images containing lines and text using ImageMagick. You'll find more techniques in our articles "Graphics from the command line" and "More graphics from the command line." You'll also find a host of other examples at the ImageMagick home page. See Related topics for links.

The scripts here are not bullet-proof. For example, we do not validate that length is a positive number big enough for a meaningful ruler or that the file specified is a valid image file type for ImageMagick. You will find other issues as well. For example, the ruler labels may be truncated as in Figure 10 above, a possibility our script did not consider.

You can also parameterize as many aspects as you wish. Try adding color or ruler height as parameters, for example.

While this tip has focused on using ImageMagick with Linux, ImageMagick is available for other platforms including Windows. Try using these techniques with your favorite scripting tools on your favorite platform.

Downloadable resources

Related topics