February 3, 2014

How to Resize, Rename, Sort and Proof Photos from the Command Line

Today's ImageMagick lesson covers how to resize images, change case on file extensions, convert file formats, construct a proof sheet of thumbnails, and search and sort photos by their Exif data.

The ImageMagick suite of image processing and manipulating commands has been around forever, and lurks in all kinds of places: it is the image-processing backend in Drupal, Lyx, OpenShot, and many more. ImageMagick is über-powerful, and because it is a command-line program you can build clever scripts with it and automate routine tasks. You can flip, mirror, resize, distort, shear, and rotate images; do special effects, edit colors, and draw lines and shapes; create thumbnails, galleries, and proof sheets. It supports a skillion image formats and has APIs for a horde of programming languages, such as MagickCore (C), MagickWand (C), Magick++ (C++), JMagick (Java), RMagick (Ruby), TclMagick (Tcl/TK), and several more.

The most worthy use of ImageMagick I have ever seen is to manipulate images for the Upside-down-Ternet, which is a slick script that uses iptables and ImageMagick to have fun with freeloaders who poach your wi-fi. It re-directs pages to Kittenwar! and mangles images on other pages.

man imagemagick lists all the commands. There is no imagemagick command. The display command opens a graphical editing interface (fig. 1).

fig-1 The view from Carla's back door in Imagemagick.

The original image was too large, so I resized it with convert:

$ convert fig-1.png -resize 550x fig-1-s.png

The resize value is the width in pixels. Width always comes first, and you can make sure by putting an x after the value, with no space. When you specify only the width then your new image will be exactly that width, and the height will automatically be in proportion. When you want to specify the height, give the height value prefixed with an x, like this:

$ convert fig-1.png -resize x250 fig-1-s.png

One thing you have to watch with the various ImageMagick commands is overwriting your original images. With convert you preserve your original and create the new file by giving it a different name.

Stop Shouting

Windows and a lot of cameras are like old people on Facebook: they shout at you in uppercase. My Canon camera writes filenames in uppercase, so they look like WP7_4117.CR2. You can easily make all file extensions lowercase with this Bash one-liner:

$ for file in *.CR2; do mv $file ${file%%.CR2}.cr2; done

Convert Formats

The convert command converts image file formats. You can modify the upper-to-lowercase incantation to batch-convert formats, like this example that converts .png to .jpg:

$ for file in *.png; do convert $file ${file%%.png}.jpg; done

This preserves your originals and creates new images in the new format with the same filename, like this:

$ ls

The default quality level for .jpg files converted from other formats is 92. You can control it with the -quality option with values from 1 to 100, with 1 being maximum compression and crappiest quality, to 100 which is best quality and least compression. This example uses 75, which is a decent level for Web images:

$ for file in *.png; do convert -quality 75 $file ${file%%.png}.jpg; done

Proof Sheet

Now that you have a nice batch of images to admire, make a proof sheet so you can admire them all in a single .jpg:

$ montage -label '%f'-geometry 350x+7+7 '*.jpg' proof-sheet.jpg

This results in something like figure 2, with each image 350 pixels wide, borders of 7 pixels, and the filenames of each image. If you like a nice frame instead of a plain border, -frame [value in pixels] creates a border around each image, and -mattecolor [color] lets you select the border color if you don't like the default meh gray. Color Names tells you the color codes.

fig-2 photo montage

As with all ImageMagick commands there are a squillion options. Use -pointsize 12 to set a font size of 12 points, or other sizes as you desire, and these options let you fine-tune the labels:

  • %b -- file size on bytes
  • %m -- file format
  • %G -- image dimension in pixels
  • %Q -- image compression level.

There are way more, and you can find them all at ImageMagick Escapes.

Extracting Exif

The identify command reads the Exif data of photographs. You can easily view a basic set of information:

$ identify kitten.jpg
kitten.jpg JPEG 3648x2736 3648x2736+0+0 8-bit DirectClass 4.402MB 0.000u 0:00.000

Want to see everything there is to know about your photos? Try this:

$ identify -verbose kitten.jpg

That is a deluge of data, so you can fine-tune it to look for specific information, like which photos with the .cr2 extension in the current directory were taken with a flash:

$ for file in *.cr2; do identify -format '%[exif:flash] [%f]' $file; done
16 [WP7_4272.cr2]
16 [WP7_4273.cr2]
9 [WP7_4274.cr2]
9 [WP7_4275.cr2]

So...9 and 16. Okay. What do those mean? The Exif flash tags are combinations of the following values:

0:  FlashDidNotFire
1:  FlashFired
2:  StrobeReturnLightDetected
4:  StrobeReturnLightNotDetected
8:  CompulsoryFlashMode
16: AutoMode
32: NoFlashFunction
64: RedEyeReductionMode

So 16 means the camera was in auto flash mode, but the flash did not fire. 9 means compulsory flash mode, and it did fire (8 + 1). So you can add an egrep incantation to find only the images where a flash was fired:

$ for file in *.cr2; do identify -format '%[exif:flash] [%f]' $file | egrep ^'1 |9 |17 |65 ' ; done

Note the spaces after each number in the egrep search pattern. That limits your search to those exact numbers, and filters out larger numbers like 11 and 9897 and such.

You can dig up any Exif tag you want with identify, and ImageMagick Escapes describes dozens of escapes to use. But it's not a complete list, so the quickest way to see what tags you can search on is to run identify -verbose [filename], and then look at the output. It's going to be different for different image file formats, and it's going to differ according to whether the images have been edited, and the software used. Here is an abbreviated example:

$ identify -verbose WP7_4275.cr2
    date:create: 2014-01-29T16:35:34-08:00
    date:modify: 2014-01-29T16:35:34-08:00
    dng:Aperture: F5
    dng:FocalLength: 28.0 mm
    dng:ISOSpeed: 640
    dng:Lens: Canon EF 24-105mm f/4L IS
    dng:Model: EOS 7D
    exif:DateTime: 2013:12:25 15:59:28
    exif:Flash: 9
So you could extract some of these this way:
$ identify -format '%[exif:datetime] %[dng:aperture] %[dng:lens] %[dng:model]' WP7_4275.cr2        
2013:12:25 15:59:28 F5 Canon EF 24-105mm f/4L IS EOS 7D 

This gives you a powerful way to find photos with particular traits, such as lens, camera, flash or no flash, date, focal length, size, bit depth...if it's in Exif you can find and sort it.

One more fun Exif tip: if you're going to post photos online, you might want to strip the Exif data so you don't give away too much information. You can do this with the mogrify command:

$ mogrify -strip kitten.jog

Or strip a whole directory of photos:

$ mogrify -strip /imagesforweb/*

Weird and true Exif fact: Even though it is a widely-used standard, it is not maintained by anyone. The current version, 2.3, was released in 2010 by Japan Electronics and Information Technology Industries Association (JEITA) and Camera and Imaging Products Association (CIPA). Since then it has been orphaned.

Click Here!