This site uses cookies to ensure you get the best experience. More info
Got it!

Getting colorful



The problem, again

In my previous post I highlighted a problem about the limitations of Malison and how I refactored it in order to improve the library’s flexibility.

The problem similar to the one I’m going to go over today, was related to a tight coupling between the library’s core and unnecessary abstraction.

This time the abstraction in question is the TermColor enumeration.
The idea was, again, to introduce a common mapping over some general values, in this case - colors.

The huge problem here was the even more apparent limitation of such type enumeration.

Still, the Character structure which represents a text character with foreground and background relies on some kind of color.

The solution

I decided to not scale down and implement a full-fledged structure containing the basic RGBA and HSL values for easy color manipulation, transformation and access.
Along with that I have implemented methods for color manipulation and palette generation.

I probably have found some small library for that but I got really into it and decided to just have some fun.

Let me show you what is now possible with new TermColor structure.

Quick access to common colors

To limit refactoring to minimum, I have implemented static properties returning certain common colors such as Black, White, Red, Green, Blue and so on.

This is compliant with the syntax of getting for example the Black value of TermColor enumeration and the helper methods themselves are pretty useful.

Color transformation methods

To create colors programmatically and quickly create, for example different shades of certain colors I have implemented a number of methods to help users do that.

It’s worth to keep in mind that such methods always return a new TermColor structure, leaving the source intact. Just like the commonly known DateTime in C#.
Another tidbit is the usage. I implemented both the instance and structure methods so the user can use whichever he feels more comfortable with.
I’ll be only including the structure methods, hence the instance methods are directly calling them too.

Lightening/Darkening

Lightening a color means increasing the L in color’s HSL values.
We clamp the L value between 0.0 and 1.0 so we do not run into any problems with generating the colors.

public static TermColor Lighten(TermColor source, double amount)
{
    return TermColor.FromHSLA(source.H, source.S, Math.Max(Math.Min(source.L + amount, 1), 0), source.A);
}

Darkening is the opposite, here we subtract a certain amount from the source’s L value. In order to do that, we just call the Lighten method with negative amount supplied.

public static TermColor Darken(TermColor source, double amount)
{
    return TermColor.Lighten(source, -amount);
}

Saturating/Desaturating

Saturating changes the S attribute. Saturation is kind of a level of colorfullness in our colors. A grayscaled image is represented by colors, that have saturation equal to zero.
Similarly, we increase the color’s saturation and clamp the values to avoid incorrect input.

public static TermColor Saturate(TermColor source, double amount)
{
    return TermColor.FromHSLA(source.H, Math.Max(Math.Min(source.S + amount, 1), 0), source.L, source.A);
}

Desaturating being the opposite of saturating should be quite straightforward now as you probably get the pattern.

public static TermColor Desaturate(TermColor source, double amount)
{
    return TermColor.Saturate(source, -amount);
}

Speaking of saturation and desaturation there is also the Grayscale method that simply desaturates the color by 1.

public static TermColor Grayscale(TermColor source)
{
    return Desaturate(source, 1f);
}

You may notice a lack of RGB values here, which are basic and common for color representation and ask yourself, why is that? Why operate on some fancy HSL and not just RGB?

The reason for that is color manipulation and palette generation is very easier to do when operating on HSL. Just like you saw above, making any color Gray scared or lighter/darker is a straightforward operation in HSL, while in RGB it’s quite not intuitive.

Why use RGB then at all? The answer is - because you kind of have to. The displays and all pixels you see are based on RGB, and that’s how the colors are passed in order to render them. If we would keep only the HSL values, we would sooner or later have to convert them to RGB so our rendering framework would understand it.
This is why the TermColor structure keeps the track of both. This may not be the most optimal approach memory-wise but simplifies the calculations which are only made while creating the color from a set of either RGBA or HSLA values.

I’m not entirely set here and I’m wondering about if and how I should change it, but even if I would remove those, the HSL values would have to be calculated either way to supply easy and rich API for color manipulation.

Mathematical operators

Still, there is a place where I actually do use RGB values, and that is within the implemented operators.

I have implemented the addition, subtraction and multiplication operators to quickly create new colours. For example, if we add green to red we get yellow, if we add green to blue we get cyan.
Definitely those are a bit less useful but as it’s a library, a rich API is still a good thing.

Palette generation

Speaking of the more interesting API methods I implemented, the palette generation were the ones I had the most fun with.

Gradients

Let’s start with gradients. Gradient is basically a way to represent a transition from one color to the other.
The Gradient method takes a source color, a destination color and the amount of steps.

Given those parameters, the user receives a collection of TermColor instances that represent the generated gradient.

The code for generating the gradient is based on RGBA values and not HSLA. That’s because the simple stepping for HSLA would not generate an expected result - it would go wildly through the color wheel and throw some unexpected colors in between.

public static IEnumerable<TermColor> Gradient(TermColor source, TermColor destination, int steps)
{
    int stepA = ((destination.A - source.A) / (steps - 1));
    int stepR = ((destination.R - source.R) / (steps - 1));
    int stepG = ((destination.G - source.G) / (steps - 1));
    int stepB = ((destination.B - source.B) / (steps - 1));

    for (int i = 0; i < steps; i++)
    {
        yield return new TermColor(source.R + (stepR * i), source.G + (stepG * i), source.B + (stepB * i), source.A + (stepA * i));
    }
}

Example of generated gradient A simple example of a generated gradient usage.

Triad

The triad supplies good and distinctive colors, which you will see for yourself in a second. The triad is represented by drawing a triangle on the color wheel, and taking the colors from the triangle’s edges.

To give you an image of how that looks like let’s take a look on the color wheel itself.

Behold, the color wheel Behold, the color wheel.

While it may seem a bit strange at first, the color wheel is quite a handy tool. Whenever we rotate our position on the wheel, we change the hue of the color. Hue basically defines if our color is greenish or reddish or blueish. So, if we set our starting point somewhere on the wheel and then draw a triangle that has an edge in that point, we get a triad.

Triad representation on color wheel Triad representation on color wheel.

To generate a triad with as best distributed colors as possible we would draw an equilateral triangle, that means we would have to go 120 degrees in one side, and 120 degrees in the other ( or just 240 degrees in the same way) to get the two other colors.

public static IEnumerable<TermColor> Triad(TermColor source)
{
    for (int i = 0; i < 3; i++)
    {
        yield return TermColor.FromHSLA((source.H + i * 120) % 360, source.S, source.L, source.A);
    }
}

Example of generated triad Example of generated triad. Notice the easily distinctive colors.

Tetrad

Similar to to triad, this time we draw a square. The result is similar but we get 1 more color in the palette.

Tetrad representation on color wheel Tetrad representation on color wheel.

The source code is analogous, this time we move by the intervals of 90 degrees.

public static IEnumerable<TermColor> Tetrad(TermColor source)
{
    for (int i = 0; i < 4; i++)
    {
        yield return TermColor.FromHSLA((source.H + i * 90) % 360, source.S, source.L, source.A);
    }
}

Example of generated tetrad Example of generated tetrad. One more color and we can still easily see the difference between them.

Analogous

Analogous colors are close to each other on the color wheel, but differ in hue. The analogous palette is similar to generating a triad or tetrad but we can generate much more results.

public static IEnumerable<TermColor> Analogous(TermColor source, int size=6)
{
    TermColor basecolor = source;

    int step = 360/size;

    for (int i = 0; i < size; i++)
    {
        double h = (basecolor.H + i * step) % 360;
        yield return TermColor.FromHSLA(h, basecolor.S, basecolor.L, basecolor.A);
    }
}

Example of analogous palette with 10 colors Example of analogous palette with 10 colors.

Example of analogous palette with 30 colors Example of analogous palette with 30 colors.

We can see that this simple approach, while effective, will start to get more gradient-ish for bigger palette sizes.

To counter that effect we could change the implementation to accept some modifier which would be taken into account to randomly change the L and S parameters to get even more varying colors.

But for now that seems pretty much okay to me, maybe I’ll tackle it in the future

Wrapping up

And that’s pretty much the whole result of my refactoring of Malison.

I’m quite satisfied with it as it allows me to do so much more with the library, now that we can support any kind of characters and colors with ease.
I’ll be able to put more time into Praedium now and hopefully I’ll not run into more limitations of my chosen dependencies, which I also maintain.

I have also thought of extracting all the color related code in to some other, very small library. Maybe someone else could use it, without necessarily needing the whole terminal emulation package?

Maybe I’ll give it a go in the next days.

I hope you enjoyed this colorful post and maybe got interested in all the math behind it. The idea of procedural color palette generation is very intriguing so it’s very likely I’ll be expanding this color module in the future and write a post or two about it.

Have a nice day and see you next time!