Work the Shell

Watermarking Images—from the Command Line

Dave Taylor

Issue #276, April 2017

It's pretty darn easy to analyze and manipulate images from within a shell script.

Us geeks mostly think of the command line as the best place for text manipulation. It's a natural with cat, grep and shell scripts. But although you can't necessarily view your results from within a typical terminal window, it turns out to be pretty darn easy to analyze and manipulate images from within a shell script.

In my last article, I introduced the splendid open-source ImageMagick suite that offers more features and functionality than you can shake a tree's worth of branches at. Why you would be shaking a tree at a piece of software escapes me, but, hey, I'm just writing this stuff, not thinking about what I'm saying.

Um...wait a sec.

Anyway, ImageMagick includes a variety of programs that let you analyze, manipulate and even convert image files in a remarkable number of different ways.

My last article described setting things up and a few easy ways to confirm that the suite was working correctly on your computer. Now let's start with a simple task that can be useful for web servers: a script that checks image files and flags any that are more than a specific size.

In fact, let's use 8MB because that's the maximum size allowed in Facebook Open Graph, a fact of which many webmasters are already well aware.

Finding Those Big Darn Image Files

Identifying big files can be done with a simple find, but the goal here is to do something more sophisticated, so let's pair it with the ImageMagick command identify.

Here's a loop to identify files bigger than a specified size:

for name in `find *{png,jpg} -size +8M -print`
do
    echo file $name is bigger than 8MB
done

That's a good start, but for those image files that match, more detail would be helpful, and I'm not talking ls -l output! Instead, let's replace the rudimentary echo statement with something more advanced:

dimensions=$(identify $name | cut -d\  -f3)
size=$(identify $name | cut -d\  -f7)
echo "File $name ($dimensions) has file size $size"

Now let's have a quick test:

$ sh bigimages.sh
File screenshot-6.png (1800x490) has file size 9.639MB
$

It's definitely more informative than just doing an ls -l and particularly so if you were to put this in an automatic email report for hosting clients!

Why? Because lots of people who aren't tech-savvy upload images directly from digital cameras—images that can be enormous. They resize the displayed image in their blog posts, but the original files are way larger than necessary, slowing down their sites unnecessarily.

Before moving on, let me make a few comments. First, notice the size test with find: -size +8M. find has a weird view of numbers and comparisons, and the test -size 8M would match only files that are exactly 8MB in size—not useful. Without the “M” suffix, the test would be comparing to 512-byte blocks, so +8 would match a lot of files (because eight 512-byte blocks is only 4K, and that's tiny for an image file). find also knows “K” for kilobytes, “G” for gigabytes, “T” for terabytes and, yes, “P” for petabytes.

The second observation to make is the use of quotes with the echo statement. Try it. Without quotes, the shell would complain about the use of parentheses, and with single quotes, it would show the variable names, not expand them. It's a good real-world reminder of the subtle, but important quote nuances in the shell!

Add a Watermark

One of the most popular uses of ImageMagick isn't to identify image dimensions but to add watermarks. You've seen watermarks on images all over the web. Sometimes it's a little copyright notice, a URL or even a tiny graphical bug that identifies the source or ownership of the image.

Nice effect, but how do you do it? Use the convert command. In fact, if you want to just add a text overlay on the bottom of an image, the command is simple:

convert $source label:'LinuxJournal.com'-append $output

The default isn't very glamorous, however, so you'll inevitably want to customize it a bit. To make the font a bit larger, use -pointsize, and to move the watermark text to be in the lower right instead of its default position, use -gravity.

This is a bit more sophisticated and shows some of the weirdness of ImageMagick:

convert $source -pointsize 80 -gravity east \
    label:'LinuxJournal.com' -append $output

This easily can be poured into a script, of course, and either you can have the output images in a different directory or you can rewrite the source filename appropriately. My favorite way to accomplish the latter is this:

predot=$(echo $name | rev | cut -d. -f2- | rev)
postdot=$(echo $name | rev | cut -d. -f1 | rev)
newname=$(echo ${predot}-wm.$postdot)

Since there's no “last field” option in cut, the way to grab just the filename suffix, even if the base filename contains dots, is to reverse the name, grab the first field, then reverse it again. This way, you can take a filename like “red.rose.png” and rewrite it as “red.rose-wm.png” to denote the version that has the watermark added.

But, what if you have a small graphic bug you want to overlay on the lower left corner of the image instead?

Let's assume the watermark graphic is specified with the variable $copy. With that in mind, a nice blend of the two images can be achieved with a 100% dissolve:

composite -dissolve 100 $copy $source $output

That will put the graphic on the top left corner of the resultant image, “above” the underlying photograph or graphic.

You can move it to the lower right by using -gravity—literally. Imagine a compass overlaid on your image: “north” is up, “east” is the right side, “west” is the left side and so on. With the convert -label command shown earlier, the default position was the bottom of the resultant image, so gravity of “east” moved the label to the lower right.

With composite, however, there's a lot more flexibility, so positioning the copyright bug in the lower right involves using a gravity of “southeast”—like this:

composite -dissolve 100 -gravity southeast $copy $source $output

Wrap that in a for loop, and you've got a handy script that can add text watermarks along the bottom or overlay a graphic watermark or copyright instead.

In both cases, notice that the ImageMagick commands always create a new output file. If you want to replace an image with a version that includes a watermark, you'd need to do a bit of file shuffling:

composite -dissolve 100 $copy $source $output
mv $output $source

Ideally though, you'd check to ensure that the composite command worked properly (rather than potentially deleting files or producing a cryptic error). Two ways come to mind: check the error status of the composite command (the sequence $?) or just test for the existence of $output. Here's the latter, since then you aren't reliant on composite having accurate return codes:

composite -dissolve 100 $copy $source $output
if [ -x $output ] ; then
  mv $output $source
else
  echo "Couldn't replace $source, $output wasn't created?"
fi

Is that good? Maybe not. The -x test checks for the existence of the file, but what would be better would be to ensure that the file exists and is non-zero size. That's the -s flag. It's a simple switch, and you've got the basis for a good script.

Magic with Images

The ImageMagick suite contains a number of commands that have dozens to hundreds of parameters. They can be very quirky, as the gravity setting shows—be glad I didn't delve into the geometry parameters—but fortunately, there are lots of online tutorials and help pages.

Remember, it all starts at www.imagemagick.org.

Dave Taylor has been hacking shell scripts on UNIX and Linux systems for a really long time. He's the author of Learning Unix for Mac OS X and Wicked Cool Shell Scripts. You can find him on Twitter as @DaveTaylor or reach him through his tech Q&A site: www.AskDaveTaylor.com.