While automating production of customised applications I needed to automatically create a set of icons that match the colour scheme selected by the customer. This article describes some of my experiments (details follow the strip...).

ImageMagick comic strip made with @ComicStripIt

My aim was to find a way to take a target colour and re-create the entire icon set matched as closely as possible to that input colour, using a single command-line.

Lets start with a quick look at a sample icon - this was created by our UX designer and used in building the proto-typical instance of the application. It is part of a set of around 30:

Original icon

Given that I want to be able to apply a colour selected by a customer, I need to start from a neutral state, so my first step is to de-colourise the original icon, producing this grey-scale version:

Example icon to be coloured

Note that the icons all have transparency, but otherwise are largely made from shades of a single colour (a gradient) with white highlights.

I started by looking at the simple built-in image-magick commands. Given that I'm converting a large batch of icons I'm using mogrify instead of convert, which also requires that the command-line is re-ordered slightly, for example:

convert in.png -brightness-contrast 20x20 out.png


mogrify -brightness-contrast 20x20 *

My first attempts used the Image-Magick commands tint, colorize, and +level-colors individually, as I was hoping for a very simple solution to present itself. Let's look at what each of those commands produces if we try to create icons with the following base colour:

The background here is our base colour
mogrify -fill "#0000cc" -tint 100 *

imagemagick tint

mogrify -colorize 100,0,0 *

imagemagick colorize

mogrify +level-colors "#000066","#0000cc" *

imagemagick +level-colors

As you can see from those examples, tint does the best job of retaining the fidelity of the icon, but doesn't really get close to the target colour.

Colorize has also kept most of the fidelity, but the white foreground has tended towards the target blue colour along with the grey background parts, though neither has really got very close to our intended colour.

+level-colors has got us closer to our target colour, but we've almost completely lost the white and the fidelity of the icon is, as a result, pretty much destroyed.

Reduce and re-compose

OK, so we can't get there with a simple one-liner. What about if we strip out different aspects of the image, perform different operations on each composite part, and then re-combine them later?

This is ultimately what I ended up doing:

  1. Extract the white part only
  2. Brighten the grey part (helps the later stages to get closer to the target colour)
  3. Adjust the grey (background) part towards our target colour
  4. Composite the white foreground back over the re-coloured background

Here's the commands to achieve that (note: I switched to using convert instead of mogrify because it was easier to test incremental changes this way):

# extract the white parts
convert -fuzz 60% -transparent black in.png 2.png

# lighten the original image
convert in.png -brightness-contrast 20x0 3.png

# level colours ...
convert +level-colors "#000066","#0000cc" 3.png

# composite together ...
convert 3.png 2.png in.png -composite out.png

It shouldn't be too difficult to follow that.

The first command extracts the white-ish parts of the image (foreground) by making shades of grey - from black through 60% grey - transparent. The fuzz factor is what determines the cut-off point. We produce this white-foreground as a separate image (2.png) because we still need the original for later steps.

Next we create a 3rd image (background) as a lightened version of the original (3.png) then colourise it using the +level-colors command we used earlier.

Finally we composite together the background image as the base, the foreground image on top, and use the original image as a mask so that we don't lose the transparency. The final result looks like this:


This is the best I've managed so far with my rudimentary knowledge of ImageMagick.

Since I'm invoking this conversion from a Java process I think I'll try something a little more low-level in Java next. I want the fidelity of the "tint" operation, with the precise colour targeting of the composite approach, I just don't know how to get there with ImageMagick.

blog comments powered by Disqus