The redesign of my website finally gave me an opportunity to experiment with SVG filters and color manipulation. The duotone look, which was made sort of trendy again earlier this year by Spotify's new brand identity, is based around manipulating the image's color range by modifying the individual color channels.

In this blog post I'm going to explain the idea behind the duotone effect, how to achieve it using SVGs and the much talked about feColorMatrix, and how to do and automate the matrix calculation. I'll also show how I've implemented an easy color matrix generator in my blog CMS.

A little bit of color theory

If you've ever played around with the color channels in Photoshop or a similar editor, you've probably realised that it's not actually that easy to produce nice-looking results. My first experiments were all pretty frustrating because I was mostly just moving sliders around without understanding the actual logic behind it.

You're probably familiar with the RGB model, in which red, green and blue are added in different proportions to produce more colors.

Illustration of RGB color model

The darkest color, black, represents the absence of R, G and B (rgba(0, 0, 0)) and the lightest color, white, the combination of all of them (rgba(255, 255, 255)). All other colors are created by mixing different proportions of red, green and blue. For grayscale images, the R, G, and B values are always equal – take rgba(85, 85, 85) for example, which maps to a #555 gray.

The duotone effect we're trying to achieve is basically like grayscale or "black and white": a light and a dark color with all other shades made up of the difference between them. This means that we need to modify the constant that would be black and manipulate all other shades to be on the spectrum between the two new colors — relative to the color spectrum of the original image. This is where a bit of maths and a color matrix comes in.

Introducing feColorMatrix

feColorMatrix is an SVG filter that lets you change colors by manipulating the individual RGBA (red, green, blue, alpha) channels. The color manipulations are specified in a matrix with five columns (the input RGBA values and a constant) and four rows (representing each output R, G, B, and A value). By adding up each row, it calculates a new RGBA output value.

<filter id="filter">
        values="1 0 0 0 0
                0 1 0 0 0
                0 0 1 0 0
                0 0 0 1 0"/>

To see how this works and looks in detail, I highly recommend Una Kravets' article over at A List Apart. For a more in-depth look at the maths behind the matrix calculations, check out this article on CSS-Tricks by Amelia Bellamy-Royds (who also happened to leave this very helpful comment on StackOverflow, which is basically the tl;dr version). It also explains the duotone matrix I'm talking about in this post.

Calculating the matrix

Since the R, G, and B values are always going to be equal, we only need to use one of the columns, plus the last column for the constant. All you need to do is set the R value to the difference between the R value of the lighter and darker color and the constant to the R value of the darker color. Then repeat the process for the G and B values row by row.

Here's what the matrix calculation would look like for the colors rgb(A, B, C) and rgb(D, E, F). The color matrix uses a range between 0 and 1 so we need to divide the values by 256.

(A / 256 - D / 256) 0 0 0 (D / 256)
(B / 256 - E / 256) 0 0 0 (E / 256)
(C / 256 - F / 256) 0 0 0 (F / 256)
         0          0 0 1    0

Let's automate it

For my blog, I wanted an automated solution that creates a new color matrix for each thumbnail image based on two input colors. This way, I can easily set and change those two colors in the meta data of each blog post — much better than doing it manually and much more readable than a cryptic-looking string of numbers between -1 and 1.

While browsing Codepen for inspiration, I came across this pen by José Manuel Pérez. It uses a simple JavaScript function to calculate a matrix based on two input RGB values. You can play around with it by editing the pen and calling the JavaScript function with different color combinations.

It also brought color-interpolation-filters: sRGB to my attention, which can be set as an attribute of the filter or via CSS. If you've been wondering why you couldn't find a good configuration for an optimal contrast between the two colors – this might do the trick!

While this simple DOM manipulation works great for a demo, it's obviously not the best idea to compute and re-render a matrix on the client for a bunch of images. Ideally, this should be taken care of when building the static files and thus part of the blog CMS.

Jade to the rescue

Jade is a clean templating language that compiles to HTML and supports reusable flexible content elements (mixins) and, if needed, JavaScript expressions. This is pretty cool because whenever I want to do something that's too complex to do with inline template logic, I can simply write a JS function.

I'm using Harp as my static site generator of choice since it lets me write my templates in Jade and also comes with handy out-of-the-box pre-processing. For this demo, I've simply added the post meta data to the top. If you use Harp, you would store this in the _data.json of your blog directory, which becomes available in your layout as

    "post-one" : {
        "image": "one.jpg",
        "colors": [ [255, 192, 79], [246, 22, 157] ]

    "post-two": {
        "image": "two.jpg",
        "colors": [ [122, 255, 98], [107, 17, 202] ]

    "post-three": {
        "image": "three.jpg",
        "colors": [ [122, 255, 98], [107, 17, 202] ]

Now you can loop through the posts and render the images. To calculate the matrix, it calls getColorMatrix() with the two colors defined in the meta. Here's a demo of the whole thing rendered in Jade:

Different implementations and improvements

The solution outlined above should be very easy to adapt for any other Node.js-powered blogging software. But the calculation itself is so basic, it should be fairly easy to implement for other systems as well. Here are some ideas:

  • Create a tag template for Jekyll in Ruby that performs the calculations and returns the matrix values or the entire filter markup using something like {% color_matrix (122,255,98) (107,17,202) %}
  • Add hex to RGB conversion so you can specify two hex values and even their shortened versions

Links and Resources

About the author
Ines Montani

I'm a digital native, programmer and front-end developer working on Artificial Intelligence and Natural Language Processing technologies. I'm the co-founder of Explosion AI and a core developer of spaCy and Prodigy.