Watermarking Images--from the Command Line
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!
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.
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 http://www.imagemagick.org.