Painting with Constable

October 05, 2011

ImageMagick annoys me. Not because of what it does — functionally, it’s the bee’s knees — but because installing it is a pain. Like many Ruby developers, I tend to develop on a Mac, an operating system without much of an official package manager. Installing tools with complex dependencies like ImageMagick gets tedious fast. I long for the days when I can apt-get install imagemagick while still enjoying all the lovely hardware a Mac provides.

Over the years I’d built up some solid experience with virtualisation, and then Vagrant came along and made it trivially easy to run Ubuntu on my Mac. Suddenly I had access to apt and proper ImageMagick packages. Unfortunately I couldn’t use them natively on my Mac — they were only accessible from inside the VM. Better than nothing, so for command-line image manipulation I’d been getting by.

During my more recent work I’d spent a fair amount of time with messaging, exposing services on a message bus for remote clients. There’s something really satisfying about not having to worry about the implementation of a service — just knowing that a message in a certain format sent to a certain destination will get the job done. So I tried exactly that for ImageMagick: exposing it on my VM as a service on the bus. It worked well enough that I threw up a project on GitHub and released a RubyGem. The project is called Constable — the README explains why.

Constable is very nearly a drop-in replacement for ImageMagick. After installing the gem and setting up the service, you can use the same ImageMagick commands to do a lot of the same stuff that a local install would let you do. There are some caveats, of course. Output must (at the moment, at least) be streamed to STDOUT. ImageMagick supports this by letting your output filename take the form format:-, e.g. jpg:- or png:-. There may be other shortcomings I haven’t hit, possibly because while I use ImageMagick a lot, I don’t use it in particularly complex ways. If you come across any problems, let me know. If you can submit a patch, even better.

Setting up an example service is covered in the “Up and running fast” section of the README, so I’ll skip that and run through a quick demo: creating a couple of JPEGs with text in them, compositing one on top of the other, and producing a PNG at 50% of the original dimensions.

First, make sure the service is up as described in the README.

Second — a bit of an undocumented easter egg at the moment — install the binstubs for the ImageMagick services:

sudo constable-install

Note that this will overwrite the following files if they exist:

/usr/bin/identify
/usr/bin/convert
/usr/bin/compare
/usr/bin/composite

This step is optional but gives you the same command names that ImageMagick uses. If you’d rather skip it, just prefix each command with constable- and add a double dash immediately after the command name, e.g. convert foo.jpg png:- becomes constable-convert -- foo.jpg png:-.

Now, on to actually using the service. Creating text-based images is straightforward with the convert command. Here I create two JPEGs — one in blue tones with the text “Anthony”, and another in pink tones with the text “Cleopatra”:

convert -background lightblue -fill blue -font Candice -pointsize 72 \
  label:Anthony   jpg:- > anthony.jpg
convert -background pink      -fill red  -font Candice -pointsize 72 \
  label:Cleopatra jpg:- > cleopatra.jpg

They won’t win any design awards, but they’re good enough for a demo:

  • Anthony
  • Cleopatra

To combine them, we use convert in a different invocation:

convert anthony.jpg cleopatra.jpg +append jpg:- \
  > anthony_and_cleopatra.jpg

Resulting in this magnificent creation:

  • Anthony and Cleopatra

Finally, we can convert the combined image to a PNG at 50% of its original size:

convert anthony_and_cleopatra.jpg -resize 50% png:- \
  > anthony_and_cleopatra.png

Which outputs the smaller PNG:

  • Smaller PNG

This demo works identically whether you’re running the service in a VM or using a local ImageMagick install. I’m rather happy with that. But there’s no reason to stop here — why not expose this as a proper remote service and write a plugin for AttachmentFu or Paperclip that uses it to offload image processing entirely? Get that heavy lifting out of the request-response cycle!

The code is out there. It’s rough around the edges but it works. Let me know if you find it useful, and if you’d like it to do something it can’t yet, patches and suggestions are very welcome.

Questions or thoughts? Get in touch.