I Made My Own Colour Scheme and You Can Too!

Colour scheme

Ever go looking for a new colour scheme only to scroll through hundreds without finding anything just to your liking? Have you tried making your own?

I’d considered it before but didn’t really know how. Most colour schemes just give you some RGB hex values with no indication of where they came from. I’ve tried to pick colours in RGB before and it’s really hard!

It turns out that RGB is just not a very good space to manipulate colour in. It’s intuitive for computers but not for humans. That was important for me to realize before trying to pick my own colours.

The colour space that I do find intuitive is HSV: hue, saturation and value. In this space, hue is an angle around a circle which determines which colour it is, saturation is a fraction which determines how much colour there is, and value is a fraction which determines how light or dark it is. Wikipedia has some nice diagrams of this as a cylinder.

Good starting points in HSV are each of the hue extremes 60° apart around the circle: red, yellow, green, cyan, blue and magenta.

static const struct Hsv { double h, s, v; }
    R = {   0.0, 1.0, 1.0 },
    Y = {  60.0, 1.0, 1.0 },
    G = { 120.0, 1.0, 1.0 },
    C = { 180.0, 1.0, 1.0 },
    B = { 240.0, 1.0, 1.0 },
    M = { 300.0, 1.0, 1.0 };

To play with these, I wrote a function for deriving new colours by offsetting hue and multiplying saturation and value. I couldn’t come up with a good name for it.

static struct Hsv x(struct Hsv o, double hd, double sf, double vf) {
    return (struct Hsv) {
        fmod(o.h + hd, 360.0),
        fmin(o.s * sf, 1.0),
        fmin(o.v * vf, 1.0),
    };
}

For a terminal colour scheme, there are 16 colours, divided into “normal” and “bright” variants of black, red, green, yellow, blue, magenta, cyan and white. I call them “dark” and “light” instead.

struct Ansi {
    enum { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE };
    struct Hsv dark[8];
    struct Hsv light[8];
};

I started with the 8 light colours and derived the corresponding dark colours by reducing their values.

static struct Ansi ansi(void) {
    struct Ansi a = {
        .light = {
            [BLACK]   = x(R, 0.0, 0.0, 0.0),
            [RED]     = x(R, 0.0, 1.0, 1.0),
            [GREEN]   = x(G, 0.0, 1.0, 1.0),
            [YELLOW]  = x(Y, 0.0, 1.0, 1.0),
            [BLUE]    = x(B, 0.0, 1.0, 1.0),
            [MAGENTA] = x(M, 0.0, 1.0, 1.0),
            [CYAN]    = x(C, 0.0, 1.0, 1.0),
            [WHITE]   = x(R, 0.0, 0.0, 1.0),
        },
    };
    for (int i = 0; i < 8; ++i) {
        a.dark[i] = x(a.light[i], 0.0, 1.0, 0.8);
    }
    return a;
}

I wrote code in scheme.c to convert HSV to RGB (from the explanation on Wikipedia) and produce colour swatch PNGs. The result at this point wasn’t very easy on the eyes.

ANSI colours

From there I spent a day modifying the values in each of those x calls. I wanted an earthy scheme similar to gruvbox, so I moved the hues more towards red and decreased the saturations of blue, magenta and cyan. I tweaked values and checked the regenerated PNG to see how it looked. Once I was satisfied with the blocks of colour, I loaded them into my terminal emulator and made further adjustments for readability.

This is what I came up with!

static struct Ansi ansi(void) {
    struct Ansi a = {
        .light = {
            [BLACK]   = x(R, +45.0, 0.3, 0.3),
            [RED]     = x(R, +10.0, 0.9, 0.8),
            [GREEN]   = x(G, -55.0, 0.8, 0.6),
            [YELLOW]  = x(Y, -20.0, 0.8, 0.8),
            [BLUE]    = x(B, -55.0, 0.4, 0.5),
            [MAGENTA] = x(M, +45.0, 0.4, 0.6),
            [CYAN]    = x(C, -60.0, 0.3, 0.6),
            [WHITE]   = x(R, +45.0, 0.3, 0.8),
        },
    };
    a.dark[BLACK] = x(a.light[BLACK], 0.0, 1.0, 0.3);
    a.dark[WHITE] = x(a.light[WHITE], 0.0, 1.0, 0.6);
    for (int i = RED; i < WHITE; ++i) {
        a.dark[i] = x(a.light[i], 0.0, 1.0, 0.8);
    }
    return a;
}

Colour scheme (RGB hex values)

I haven’t yet come up with a “day” version of this scheme, but setting the values to 1.0 - v seems like a good place to start. I need to wait for a day when I can sit outside in the sun and really see how it holds up.

I want to be clear that I have zero design training and just went with that looked most pleasing to me. I’m very happy with the result, and think that others could have similar experiences. I hope this inspires you to create!