I like gophers.

Gamut, a Go Package to Manage Color Palettes

· Read in about 5 min · (1030 Words)
gamut colors color palettes color themes golang

I have talked about using an algorithm to generate color palettes in my previous blog post and as promised I set aside some of my time to work on gamut, a Go package to manage and generate color palettes & themes. The original scope of the package was to provide a more Go-idiomatic and convenient interface to the color generator I presented in the post. Working on it, it became painfully obvious that Go’s standard color package is fairly minimalistic by design. There’s the fabulous go-colorful which handles all the color space conversions and computations for you, but I still thought there’s a need for a bit more syntactic sugar when facing the every-day problems working with colors and palettes.

So here it is, a quick introduction to managing colors with gamut.

Colors

gamut operates on various color spaces internally, but all color values you pass in as parameters and all return values will match Go’s color.Color interface.

Let’s start with the basics. Just for convenience there’s a hex-value parser:

1
2
color = gamut.Hex("#333")
color = gamut.Hex("#ABCDEF")
Both the short and standard formats are supported.

Use the Lighter and Darker methods to lighten and respectively darken any given color. The HCL color space is used internally for computations here:

1
2
color = gamut.Lighter(color, 0.3)
color = gamut.Darker(color, 0.1)
This code first computes a 30% lighter version of color, then darkens it by 10%.

Often you will need the complementary value of a color (the color on the opposite side of the color wheel):

color = gamut.Complementary(color)

You can also calculate the best contrast value for a given color. The following code returns the color with the highest contrast (and hence always either black or white):

color = gamut.Contrast(color)

Around the Color Wheel

To retrieve a color with the same lightness and saturation, but a different angle on the color wheel, you can use the HueOffset function:

color = gamut.HueOffset(color, 90)
You can also go in the opposite direction by using negative values.

gamut helps you creating color schemes by providing convenient functions for a color’s Triadic, Quadratic, Tetradic, Analogous and SplitComplementary values. I can recommend w3schools’ article on color schemes. It’s a nice intro with live examples if you aren’t familiar with the color wheel yet.

The following examples use #2F1B82 as the base value for color.

Triadic schemes are made up of three hues equally spaced around the color wheel:

colors := gamut.Triadic(color)

Quadratic schemes are made up of four hues equally spaced around the color wheel:

colors := gamut.Quadratic(color)

Tetradic schemes are made up by two colors and their complementary values:

colors := gamut.Tetradic(color1, color2)

Analogous schemes are created by using colors that are next to each other on the color wheel:

colors := gamut.Analogous(color)

SplitComplementary schemes are created by using colors next to the complementary value of a given color:

colors := gamut.SplitComplementary(color)

Shades, Tints & Tones

Monochromatic returns colors of the same hue, but with a different saturation/lightness:

colors := gamut.Monochromatic(color, 8)

Shades returns colors blended from the given color to black:

colors := gamut.Shades(color, 8)

Tints returns colors blended from the given color to white:

colors := gamut.Tints(color, 8)

Tones returns colors blended from the given color to gray:

colors := gamut.Tones(color, 8)

Blends returns interpolated colors by blending two colors in the CIELab color space:

colors := gamut.Blends(color1, color2, 8)

Palettes

Gamut comes with four curated color palettes: Wikipedia, Crayola, Resene and Monokai. The Wikipedia palette is an import of common colors from Wikipedia’s List of Colors. Similarly Crayola consists of colors resembling the palette of Crayola drawing crayons, while Resene refers to the color palette of the paint manufacturer. New curated palettes and importers are welcome. Send me a pull request on GitHub!

The function Colors lets you retrieve all colors in a palette:

1
2
3
for _, c := range palette.Wikipedia.Colors() {
    fmt.Println(c.Name, c.Color)
}
This will print out a list of 1609 color names, as defined by Wikipedia.

Your Own Palettes

1
2
3
4
5
6
var p gamut.Palette
p.AddColors(
		gamut.Colors{
			{"Name", gamut.Hex("#123456"), "Reference"},
			...
		})
Name and Reference are optional when creating your own palettes.

Names

Each color in the curated palettes comes with an “official” name. You can filter palettes by colors with specific names. This code snippet will return a list of all “blue” colors in the Wikipedia palette:

colors := palette.Wikipedia.Filter("blue")

Calling a palette’s Name function with a given color returns the name & distance of the closest (perceptually) matching color in it:

name, distance := palette.Wikipedia.Name(color)
This might return “Baby Blue”, depending on the color you pass in.

Mixing

You can mix two palettes. Colors with the same name and color value will be de-duplicated and only appear once in the resulting palette:

palette := palette.Crayola.MixedWith(palette.Monokai)
This creates a combined palette with all colors of the Crayola and Monokai palettes.

Perception

Sometimes you got a slice of colors, but you have a limited color palette to work with. The Clamped function returns a slice of the closest perceptually matching colors in a palette, maintaining the same order as the original slice you provided. Finally you can remix your favorite wallpapers in Crayola-style!

colors = palette.Crayola.Clamped(colors)

Generators

Working with curated color palettes is nice, but if you need to visualize a larger data set or just want to experiment with colors, gamut’s Generators are a lot of fun to play with:

colors, err := gamut.Generate(8, gamut.PastelGenerator{})
colors, err := gamut.Generate(8, gamut.WarmGenerator{})

You can even write your own generators if you implement the ColorGenerator interface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type BrightGenerator struct {
	BroadGranularity
}

func (cc BrightGenerator) Valid(col colorful.Color) bool {
	_, _, l := col.Lab()
	return 0.7 <= l && l <= 1.0
}

...
colors, err := gamut.Generate(8, BrightGenerator{})
Only colors with a lightness between 0.7 and 1.0 will be accepted by this generator.

You can read up on the inner workings of the color generation algorithm in my previous blog post.

What’s Next

I’d like to add functions to sort color slices by certain criteria. gamut provides basic support for color roles and theming, but it’s still pretty barebone. Contributions are always welcome!

Comments