
Looking for a beautiful color palette for the site? Recently installed home RGB-lights, or want to paint the room in new colors? Or bought a keyboard with color backlight and want to use it to the fullest? Whatever the situation you are in, you are probably constantly setting up color schemes.
As a programmer, I quickly wrote a few lines of code to generate random color palettes. Immediately realizing that this approach may not give the best results, I realized the “reset” button of the palette in a couple of minutes. It seemed to me that to get a great scheme you just need a little luck and patience.
I was wrong. Generating palettes from random colors sucks. From time to time a beautiful color coexists with an ugly, dirty shade of brown or yellow. Selections of colors are always either too dark, or too light and low-contrast, or sets consist of very similar colors. It was necessary to come up with another solution.
Color spaces
Let's start with the theory. Today, the following color spaces are widely used to classify colors:
sRGB
RGB stands for
Red Green Blue
. This is how displays work: they emit light in three color channels, which are mixed in different proportions to produce all kinds of colors. The value in each channel ranges from 0 to 255.
R:0, G:0, B:0
(or # 000000 in hexadecimal) is black, and
R:255, G:255, B:255
(or #ffffff ) - white.
CIE Lab

The
CIE Lab color space is wider than sRGB and includes all colors perceived by a person. It was created with the expectation of universality of perception. In other words, the distance between the colors corresponds to the subjective difference: if the values of the two colors are close to each other, then they look similar. On the other hand, two colors far apart are also perceived as quite unlike. In CIE Lab for saturated colors allocated more space than for dark and light. By the way, for the human eye is very dark green almost indistinguishable from black. In addition, this three-dimensional color space:
L
means lightness (from 0.0 to 1.0),
a
and
b
(approximately -1.0 to 1.0) are color channels.
HCL

If RGB describes how the display displays colors, and CIE Lab describes how we perceive them, then HCL is the color space that most closely describes how we think about colors. It is also three-dimensional,
H
means color tone (hue) (from 0 to 360 degrees),
- saturation (chroma) and
L
- brightness (luminance) (both parameters are measured from 0.0 to 1.0).
For calculations, I recommend using CIE Lab, and for presenting palettes to the user - HCL. If you wish, you can convert the values from these spaces to RGB.
Color space decomposition

Since I needed to get a set of unique, individual colors, we first discard those that look very similar. The color space will be three-dimensional, and the
clustering algorithm
using the k-means method is perfect for separating such low-dimensional data sets. He tries to decompose the data (in our case, the color space) into k separate regions. And then the palette is collected from the central points of the clusters in these areas. The gif shows a two-dimensional mapping of the operation of the algorithm in the three-dimensional space of the CIE Lab.
Write the code
Using the k-means algorithm
implemented on Go, the problem is solved with just a few lines of code. First, prepare the color values in the CIE Lab space:
var d clusters.Observations for l := 0.2; l <= 0.8; l += 0.05 { for a := -1.0; a < 1.0; a += 0.1 { for b := -1.0; b < 1.0; b += 0.1 { d = append(d, clusters.Coordinates{l, a, b}) } } }
I already picked up a couple of parameters and imposed certain restrictions on the generated colors. In this example, we throw out colors that are too dark (brightness <0.2) and too bright (brightness> 0.8).
Expand the newly created color space:
km := kmeans.New() clusters, _ := km.Partition(d, 8)
The
Partition
function will return pieces of eight clusters. Each cluster has a
Center
point, which is a separate color in a given space. Its coordinates can easily be converted to a hexadecimal RGB value:
col := colorful.Lab(c.Center[0], c.Center[1], c.Center[2]) col.Clamped().Hex()
Remember that CIE Lab is wider than RGB, and therefore some Lab values cannot be converted to RGB. Such values can be transformed into the most similar colors of RGB-space with the help of
Clamped
.
Full code
package main import ( "github.com/muesli/kmeans" "github.com/muesli/clusters" colorful "github.com/lucasb-eyer/go-colorful" ) func main() {
A set of eight (not so) random colors generated by this code:

Define your own color space
Add more control over the generation of colors. We can easily manage the data used for further calculations, thereby selecting the color space for your needs. Generate a pastel palette:
func pastel(c colorful.Color) bool { _, s, v := col.Hsv() return 0.2 <= s && s <= 0.4 && 0.7 <= v && v <= 1.0 } for l := 0.0; l <= 1.0; l += 0.05 { for a := -1.0; a <= 1.0; a += 0.1 { for b := -1.0; b <= 1.0; b += 0.1 { col := colorful.Lab(l, a, b) if col.IsValid() && pastel(col) { d = append(d, clusters.Coordinates{l, a, b}) } } } }
Another color space is
HSV , the letters in the name indicate hue, saturation, and value. In this space, pastel colors usually have high brightness values and low saturation values.
Here's what happened:

You can also filter colors by their saturation (chroma) and brightness to get a set of “warm” tones:
func warm(col colorful.Color) bool { _, c, l := col.Hcl() return 0.1 <= c && c <= 0.4 && 0.2 <= l && l <= 0.5 }
Result:

Package gamut
I am working on a library called
gamut , in which I will put all the pieces described here in one convenient package on Go, which allows us to generate and manage color palettes and themes. You can already try it, but it is still in work.