Drop Shadows and Snapshot Borders – Automating Image Manipulation

Many of the web projects we do have tons of images that require manipulation and processing. Although the GIMP and Adobe Photoshop are extremely powerful tools, they require a user. That would you be you, sitting in your chair, operating on images by hand with your mouse, stylus, and keyboard. You can do amazing things, but a lot of times you’re just processing over and over and over. When you want to speed things up and move on to more interesting tasks, you will turn to ImageMagick, a set of command line utilities for processing images en masse.

A nice effect that I enjoy is the “snapshotization” of digital photos.  Take a look.

Parameters I wish to adjust:

  • Rotation (arbitrary)
  • Border width and color
  • Shadow depth, transparency.
  • Background color

So what you want to do is draw a border.  ImageMagick’s “convert” has a -bordercolor and -border directives that allow you to set the color and the width.  Make sure to set -bordercolor first, because ImageMagick doesn’t parse the command line in a sane way.  It’s a small detail, but if you don’t set -bordercolor first, you’ll get gray borders every time.  Took me forever to figure out why it wasn’t working for me.

We will now rotate the image with the border.  In order to keep our image as a separate entity, i.e. with a transparent background, we need to specify -background none and then apply the -rotate directive.  Try this line without -background none to see what I’m talking about.

That’s not what we want, eh?  So remember to specify -background none first.

Next you want to use the -shadow directive in a cloned layer.  We want to create a layer in memory using the +clone directive, draw a rectangular shape under our first layer with the border, blur the layer and offset it.  The default creates a layer above our first one, which is no good, so we swap it and render it as a proper drop shadow.   See the script below for a complete example.


#!/bin/bash

BGCOLOR="#ffffff"

file="$1"

RANGE=30
number=$RANDOM
let "number %= $RANGE"
let number=$number-15

WIDTH=`identify -ping -format "%[fx:w]" "$file"`
HEIGHT=`identify -ping -format "%[fx:h]" "$file"`
STROKEWIDTH=`echo "scale=0; $WIDTH/50" | bc `
BLUR=20
SHADOW_OFFSET_X=10
SHADOW_OFFSET_Y=10
CURVATURE="0"

OUTPUT_FILE=`echo "$file" | iconv -f LATIN1 -t ASCII//TRANSLIT`
OUTPUT_FILE=`echo $OUTPUT_FILE | tr "[:upper:] " "[:lower:]_"`
echo $OUTPUT_FILE

convert -quality 90 "$file" -size ${WIDTH}x${HEIGHT} -fill none \
-background none -bordercolor "#ffffff" -border "$STROKEWIDTH" -rotate $number \
\( +clone -background black -shadow "$STROKEWIDTH"x${BLUR}+${SHADOW_OFFSET_X}+${SHADOW_OFFSET_Y} \) \
-swap 0 -background "$BGCOLOR" -layers merge +repage -resize 480x480 "${OUTPUT_FILE%.jpg}_sm.jpg"

$RANDOM is a built in random number generator which I use here to set the arbitrary rotation of the images, between -20 and +20 degrees.  It’s simple to return numbers in this range.  Take any random number, divide it by your desired range (40 in this case), and retrieve the modulo (remainder).  Then subtract half your range to get an even distribution between -20 and +20.   It’s an old trick, but it still works.

The variables $WIDTH, $HEIGHT are necessary to set the border and blur parameters.  Again, I use ImageMagick to pull in the dimensions of the image in question.

identify -ping -format "%[fx:w]" "$file"

This will get you the width of the input file. "%[fx:w]" will get you the height.

I set $STROKEWIDTH (for borderwidth) to be 1/50th of the image’s width.  That’s a pretty good number for what I’m doing.  Obviously, you can change that to something else.

I haven’t worked out exactly how ImageMagick’s blur works, but I set $BLUR at 20, which seems to work well (I’m not sure if it’s a pixel value or what).  I’ll post more when I figure out exactly what it’s doing and how to control it.

Now this script is not particularly pretty. It doesn’t take any parameters on the command line (except the filename), has no help, and does no input checking. This is a pretty quick hack to solve a simple problem for myself. Maybe later I’ll fancy it up a bit, optimize, and make a proper program out of it, but for now, it solves my problem.

If you want to process a directory of images, you can wrap it in the following:

ls *.jpg | while read file; do script_name "$file"; done

And that’s it.  Stupid simple, no?

You may also like...