# The Secrets of Colour Interpolation

This post discusses about the tricky problem of colour interpolation, and explores possible solutions. Many software and engines offer read-to-use functions to interpolate colours. In Unity, for instance, Color.Lerp is available and does its job pretty nicely. Use the interactive swatch below to see how Color.Lerp works.

There’s nothing wrong in using these functions, as long as you know what the deal with colour interpolation is.

#### Understanding interpolation

Interpolation is a technique that allows you to “fill a gap” between two numbers. Most APIs expose linear interpolation based on three parameters: the starting point , the ending point and a value between 0 and 1 which moves along the segment that connected them:

When ,  is returned. When , is returned instead. The beauty of this formula is that is easy to understand, efficient to implement, and it works in any dimension. Lerping in two dimension only requires to independently lerp the X and Y components. Lerping always returns points on the line that connects and , regardless of the number of dimensions. A standard RGB lerp can be done as such:

public static Color LerpRGB (Color a, Color b, float t)
{
return new Color
(
a.r + (b.r - a.r) * t,
a.g + (b.g - a.g) * t,
a.b + (b.b - a.b) * t,
a.a + (b.a - a.a) * t
);
}

x

If it’s true that linear interpolation works as expected in three dimensions, the same cannot say for colours. There’s a fundamental difference between the XYZ and RGB spaces: the way the human eye perceive colours. While it make sense to connect two points in a 3D space with a line, the same doesn’t always apply for points in the RGB space. Interpolating the R, G and B components independently offers no guarantee on the hue of the intermediate colours. As Stuart Denman highlights in his Improve Color Blending, the RGB space of cyan and red meet halway in grey. A new hue appears because the RGB space does not capture how Humans perceive colours very well.

## Hue Interpolation

A first attempt to compensate for this is to switch to different colour space, such as HSV (also known as HSB). It has been designed to be “artist-friendly”, grouping colours by hue and ignoring how they are created on screen.

x

The result, as it can be seen above, is rather disappointing. The reason is that interpolating the H component cycles through different hues. In this case we don’t have to go through green, but looping over the H space in the opposite direction.

To implement an HSV lerping function we need to understand how these components are handled. For this example, we’ll assume all the HSV components range from 0 to 1. The following code is inspired from Improved Color Blending and relies on the ColorHSV Unity extension by C.J. Kimberlin:

public static Color LerpHSV (ColorHSV a, ColorHSV b, float t)
{
// Hue interpolation
float h;
float d = b.h - a.h;
if (a.h > b.h)
{
// Swap (a.h, b.h)
var h3 = b.h2;
b.h = a.h;
a.h = h3;

d = -d;
t = 1 - t;
}

if (d > 0.5) // 180deg
{
a.h = a.h + 1; // 360deg
h = ( a.h + t * (b.h - a.h) ) % 1; // 360deg
}
if (d <= 0.5) // 180deg
{
h = a.h + t * d
}

// Interpolates the rest
return new ColorHSV
(
h,			// H
a.s + t * (b.s-a.s),	// S
a.v + t * (b.v-a.v),	// V
a.a + t * (b.a-a.a)	// A
);
}

x

For comparison, the linear lerping through HSV space is also shown together with the corrected lerping (HSV*).

## Luminosity Interpolation

Despite all the effort, the transition still doesn’t look good. The reason is that even if we have correctly learped through the Hue component, different colours have different luminosities. As explained by Gregor Aisch in How To Avoid Equidistant HSV Colors, equidistant colours in the HSV space are not perceived as really equidistant. Even HSV colours with the same brightness (V) can differ in their perceived brightness and luminosity. Many aspects are responsible for this. The R, G and B components of a colour contributes in different ways the perceived luminosity, due to the way their respective photoreceptors work. Several attempts have been made to capture the non-linear relationships between R, G and B in a colour model. One of the most successful is the LCH (also known as HCL for Hue, Chroma and Lightness). Equidistant colours in the LCH space are also perceived as equidistant. The swatches below clearly shows how the LCH space provides a more uniform distribution of the colours.

x

The conversion from RGB to LCH is very expensive. This is because colours have to be converted into to intermediate spaces, the XYZ and LAB. A very good library which supports all of these conversions is chroma.js.

Using colours with equidistant perceived luminosity is essential for all these applications in which colours have a precise meaning, such as diagrams and heatmaps. Providing uniform luminosity is also important for colour blind people, as discussed in Accessibility Design: Color Blindness. A starting point to design a safe colour palette is ColorBrewer.

## Conclusion

Interpolating colours by lerping their RGB components is the most common and lazy easy approach to tackle a very complex problem. If the interpolated colours need to be visible at the same time (for instance in a chart or a diagram) chances are you might need a more advance technique. Conversion from RGB to HSV are supported by most frameoworks, but if you want to go the extra mile you should adopt the LCH colour space.

x

This post was strongly inspired by the many works of Gregor Aisch.

https://vis4.net/blog/posts/avoid-equidistant-hsv-colors/

# Interactive Graphs in the Browser

Having worked both as a teacher and an artist, I know how important data visualisation is. This tutorial will teach you to create interactive network graphs with Python and JavaScript. You can have a go by dragging the nodes of the graph below…

You can find a high resolution version of the melancoil tree (2000x2000px, first 1000 numbers) here: PNG, SVG, HTML. Continue reading

# Recreational Maths in Python

This post is for all the developers and mathematicians out there that are curious to explore and visualize the bizarre properties of numbers. Although Maths plays an important role in today’s technology, many people likes to abuse it for recreational purposes. Part of the appeal of Recreational Maths lies in the challenge to discover something new. Despite what many believe, finding mathematical patterns is very easy; it’s discovering something useful that is incredibly challenging. If you’re up for such a challenge, this tutorial will teach you how to use Python to calculate some of the most infamous numerical sequences.

# How to generate Gaussian distributed numbers

In a previous post I’ve introduced the Gaussian distribution and how it is commonly found in the vast majority of natural phenomenon. It can be used to dramatically improve some aspect of your game, such as procedural terrain generation, enemy health and attack power, etc. Despite being so ubiquitous, very few gaming frameworks offer functions to generate numbers which follow such distribution. Unity developers, for instance, heavily rely on Random.Range which generates uniformly distributed numbers (in blue). This post will show how to generate Gaussian distributed numbers (in red) in C#.

I’ll be explaining the Maths behind it, but there is no need to understand it to use the function correctly. You can download the RandomGaussian Unity script here.

# Understanding the Gaussian distribution

Randomness is so present in our reality that we are used to taking it for granted. Most of the phenomena which surround us have been generated by random processes. Hence, our brain is very good at recognising these random patterns. And it is even better at spotting phenomena that should be random but they actually aren’t. And this is when problems arise. Most software such as Unity or GameMaker simply lack the tools to generate realistic random numbers. This tutorial will introduce the Gaussian distribution, which plays a fundamental role in statistics since it is at the heart of many random phenomena in our everyday life.