Changing the accent colour of ICNS icons

One of my key tools is nvALT, a notetaking app for macOS. It’s a fork of an older app called Notational Velocity, and the icon is a greyscale version of the original:

nvALT icon Notational Velocity icon
The icon of nvALT (left) and Notational Velocity (right).

For a while now I’ve wanted to create versions of the Notational Velocity icon in different colours. Adjusting the colour of an image with a single accent colour is something I’ve done before, and it would be more exciting than a monochrome icon. Given the renewed sense of fun in Apple’s icons for macOS 11, I spent a couple of hours trying to find a way to play with icon colours.

The structure of an icon

On macOS, icons can be displayed at a wide variety of sizes. In Finder, an icon can range all the way from 16×16 to 512×512 pixels. It’s hard to draw an image that looks good in all these sizes, so macOS allows designers to include multiple sizes of an icon.

Icons are stored in the ICNS file format, which is a container with multiple images at different sizes. When the OS needs to shows an icon at a given size, it picks the closest size from the images in the ICNS file, and scales up or down as appropriate. The big sizes can contain lots of detail, and the smaller sizes can be simplified so they remain clear.

For example, the Notational Velocity icon has six sizes in its ICNS file:

Six different sizes of the nvALT icon, from 16x16 to 512x512.

Notice how the 32×32 and 16×16 sizes have a different shape, doing away with the text sheets. This allows the rocket shape to remain clear, even at a tiny size.

If you want to edit an ICNS file, you need to edit all of these sizes individually. You can edit ICNS images in Preview.app, but this is a manual process that involves lots of pointing and clicking. I wanted to find other ways to do it.

(Windows does something similar – it uses the ICO format for icons, which also contains multiple images at different sizes.)

Playing with Pillow

When I’m working with images, I often reach for Pillow, the Python imaging library. I’ve used it for several projects, and I’ve written blog posts that explain some of that code.

Pillow supports ICNS files, but it’s a bit awkward – Pillow really wants to work with one image at a time, and the “multiple sizes in one file” model of ICNS is a poor fit. I got it working, but the code’s not especially easy to follow.

I started writing about how to work with ICNS files in Pillow, and it got quite complicated for what felt like a simple task. I remembered the Zen of Python“If the implementation is hard to explain, it’s a bad idea” – and I went back to the drawing board.

Investigating iconutil and ImageMagick

I looked at the Pillow implementation of ICNS, and discovered that it shells out to a command-line tool called iconutil. This is a macOS utility which works with ICNS files, allowing you to convert an ICNS file into a folder of images (an “iconset”) and vice versa.

Here’s an example:

$ iconutil --convert iconset --output Notality.iconset Notality.icns

$ ls Notality.iconset/
icon_128x128.png icon_256x256.png icon_48x48.png
icon_16x16.png   icon_32x32.png   icon_512x512.png

$ iconutil --convert icns --output Notality2.icns Notality.iconset/

There are all sorts of tools that can operate on individual PNG images, and since I’m on the command line already I decided to try ImageMagick. The ImageMagick docs have a page describing lots of ways to modify image colours, and I discovered the previously unknown-to-me -modulate flag. This allows you to modify an image in the HSL colour space (hue, saturation, lightness).

One of the examples of the -modulate flag shows an image of a red rose being cycled to different colours (cyan, blue, pink, and so on), which is exactly what I’m trying to do. I can run that command against the Notational Velocity icon:

$ magick Notality.iconset/icon_512x512.png -modulate 100,100,0 Notality.iconset/icon_512x512_cyan.png

And here’s the result:

The Notational Velocity icon. A cyan-tinted version of the Notational Velocity icon.
The Notational Velocity icon before (left) and after (right) having the ‑modulate 100,100,0 transformation applied.

If I run this transformation over every PNG image in the iconset, then rejoin them back into an ICNS file, I’ll have my hue-adjusted icon.

Here’s what that looks like:

#!/usr/bin/env bash

set -o errexit
set -o nounset

# Apply an ImageMagick -modulate transformation to every size of an ICNS image.
#
#   $1 - path to the ICNS to transform
#   $2 - argument for the ImageMagick `-modulate` flag
#
function modulate_icns() {
    icns_path="$1"
    modulate_arg="$2"

    # Get the filename, filename before extension
    # https://stackoverflow.com/a/965072/1558022
    filename=$(basename "$icns_path")
    icon_name="${filename%.*}"

    iconset_path="$icon_name.iconset"
    out_path="$icon_name-$modulate_arg.icns"

    # Unpack the ICNS as individual images
    iconutil --convert iconset --output "$iconset_path" "$icns_path"

    # Apply the -modulate transformation to every individual image
    find "$iconset_path" -type f -exec magick '{}' -modulate "$modulate_arg" '{}' \;

    # Join the images back together into a single ICNS file
    iconutil --convert icns --output "$out_path" "$iconset_path"
}

modulate_icns "Notality.icns" "100,100,0"

Then I combined that with a bash for loop to get the icon adjusted to a variety of colours:

for (( hue_rotate=0; hue_rotate<200; hue_rotate++ ))
do
  modulate_icns "Notality.icns" "100,100,$hue_rotate"
done

And voila, I have a rainbow of rocket icons:

A grid of multi-coloured Notational Velocity icons

And because it edited every size of the ICNS file, the smaller icons all match:

A grid of multi-coloured small Notational Velocity icons

Look at all that colour!

Closing thoughts

To apply the icons, I open the app in Finder and select “Get Info”. Then I drag the ICNS file over the icon preview. This applies the new icon, and flushes whatever icon cache macOS uses for the Dock, the Finder, Spotlight, and so on.

On the Mac I’m using to write this post, I’ve picked a blue icon that matches my Desktop wallpaper:

The nvALT Dock icon with a blue rocket set against a blue background

When I was younger, I spent a lot of time choosing and customising my icons. That gradually dropped off, and until today I was running completely stock icons. I’m not about to go back to hand-picking my icons, but it’s fun to have a new way to customise and liven up my Mac’s appearance.