qr code All content created by a human. No AI.

BIT-101

Bill Gates touched my MacBook Pro

Palettes


Color is hard

Most of what I do is in black and white. I love the way it looks. I like building up forms and shapes and patterns using only values.

Also… color is hard.

Or, it seems hard. Working with color reminds me of composing music. There are so many rules, and it’s all based on physics, and also all based on human perception, and also psychology - though that last one is a bit questionable.

But I’m trying to learn new things, so I did a deep dive into colors and palettes recently.

My bitlib coding library already had a pretty rich set of color functions. I’ve coded up color creation in RGB with floats (0-1) or ints (0-255), HSL, HSV, CMYK, all named CSS colors, functions for working with brightness and contrast and mapping new colors to an existing image.

But whenever I did use color I’d most often just use my RandomRGB function to generate random colors. It mostly ended up looking like crap.

Occasionally I’ll spread things out across a spectrum, like this:

Or, if I’m feeling really adventurous, I’ll use HSV with a single hue and random saturations and hues for a monochrome composition

That’s kind of cool, but I’ve always felt like I’m missing something.

I even wrote a color palette module a while back. But never did anything with it. Mostly because it had such limited functionality. You could create a palette and then manually add colors to it. Then… use those colors … somehow, later. Basically it was just an array of color values. The only real useful thing I did with it was let you map a palette to an image. So all the dark colors would map to the first few colors in the palette, and the lighter ones to colors at the end of the list. This was pretty fun because I could make a grayscale image and then map any palette to it.

Like, I could take this picture of my dog…

Blur it, grayscale it, and map a monochrome palette to it and get this:

But creating a palette was a pain. So I still never used it. So I decided to do some work on it.

Creating a useful palette module

I started doing some research on color palettes, or color themes or color schemes. All basically the same thing - a limited set of color values that supposedly work well together. A lot of this I already knew, but for the first time tried to implement some of it in code.

So over the course of a day or two I sat down and wrote a bunch of palette creation methods.

Monochrome

The first one I did was the monochrome palette. This was similar to what I did above. But in the above I was choosing a new random color for each triangle I drew. The interesting thing about a palette is that it’s usually more limited. A palette of two colors is totally valid. Often they only contain a handful of colors. A couple dozen might be a lot, but it depends on what you’re doing I guess.

Anyway, my method takes a hue and the number of colors you want in the palette. Rather than randomly creating color values, it spreads them out across a range of saturation and values. In pseudocode…

function MonoPalette(hue, n) {
	pal := NewPalette()
	for (i = 0; i < n; i++) {
		sat = i / (n - 1)
		val = 1.0 - s
		// avoid 0 saturation or luminance, which is just grayscale
		sat = map(sat, 0, 1, 0.4, 1)
		val = map(val, 0, 1, 0.2, 1)
		pal.Add(HSV(hue, sat, val))
	}
	return pal
}

I create a new palette and loop through n times.

Saturation is i / (n - 1) which gets it to range fully from 0 to 1, and value will spread oppositely from 1 to 0. But when I had that, I found that the zero saturations were just white, and the zero values were just black. So I used a map function to map those values to keep them away from zero. The values I ended up with were through trial and error and what I thought looked good. Most of the other palette methods I made were pretty similar to this: create a palette, calculate a number of colors, add them to the palette, return the palette.

Here’s a monochrome palette with six colors.

Although this uses the same hue as the version above, it feels more monochrome-y to me. But that’s what’s great about coding your own library. You can do whatever feels right.

Achromatic

After that it was easy to create an achromatic palette. In other words, grayscale. In the past I would have just chosen random values of gray for each triangle, like so:

And this looks fine. But here, I just use a 5-color achromatic palette:

I’m not claiming it looks better, but it’s different. Fewer shades, more contrast, cripser. And now I have control. I can make a straight up black and white palette, or one with as many shades as I need for something to look the way I want.

I won’t show pseudocode for all these examples (the actual Go code is linked above). But this was pretty similar to the technique of the monochrome. Iterate n times across gray scale values from 0 to 1 and add each one of those colors to the palette.

Color Theory Cognitive Dissonance

Next I went for the common color-wheel-based palettes you read about in color theory. I might have a bit of a rant here, so be forewarned.

These palettes include complementary, split-complementary, triadic, tetradic, and a few others. They are defined by the angles between the various colors within the palette, as seen on the color wheel.

Complementary colors are those right across from each other on the wheel, or 180 degrees.

Split-complementary means you go across, and then choose the two colors 30 degrees to either side of that. So 150 and 210 degrees.

In in a triadic palette, you choose three colors that are 120 degrees from each other - they form an equilateral triangle. And tetradic contains four colors 90 degrees apart, forming a square.

Now in any text about color theory, you’ll read all about why these particular arrangements are so important. About harmony and tension, contrast and resolution. Some might go into wavelengths, others into psychology, how the various colors, by being in those exact relationships and angles with each other set up particular feelings and emotions in the brain.

But here’s the thing. Most of those texts are referring to the RYB color wheel. That’s the one you learned in elementary school. Red, yellow, blue as primary colors. Mix them to get orange, green, and purple, the secondary colors. Mix again to get the tertiary colors, which I won’t show here.

Each of these colors is 60 degrees apart from its neighbor on the wheel.

So in this color wheel, red and green would be complementary. As would yellow and purple, and blue and orange.

Triadic colors would be red, yellow, blue, or purple orange green. Each one of the colors in each triad is 120 degrees from its neighbors.

Of course there are a huge number of other triadic and complementary palettes. But since my wheel only shows six colors, that’s what’s I’ll mention.

You’d need to have the tertiary colors to get split complementary and tetradic.

All good so far.

But then we get computers. And RGB and HSL/HSV. And we get the RGB color wheel.

With this wheel, the primary colors are red, green, blue, and secondary are cyan, yellow and magenta. These are also the two triadic palettes you can get with just these six colors. Complementary colors are red and cyan, yellow and blue, magenta and green.

Now my problem is not that they are different. That’s fine. Different models, different paradigms, different technologies, additive vs subtractive, etc. I get it. All good.

My problem is that artists have gone on for years about, for example, the importance of using complementary colors and how important they are and the effects they have on the viewer. And they’re talking about, say, red and green. And now we come along with our RGB and people are still talking about complementary palettes and how these two very specific colors are going to affect your brain. But they’re talking about red and cyan instead of red and green.

I actually saw a site that went over all of this, complementary, triadic, tetradic, split, analogous and all the reasons to use each one, showing them on a static RYB color wheel. Then said, click here for an interactive color wheel you can explore these on. And it went to an RGB color wheel!

In short, I agree with the general ideas of these color palettes - it can be good to have two very different colors, or two similar colors and one different, or three or four fairly different colors. But to say that these exact angles on the color wheel magically ignite our psyches into feeling a certain way is a bit of a stretch, especially when those angles give you totally different colors depending on which wheel you are using.

Anyway, end of rant. On with the show!

More color palettes!

For a bunch of these, HSV or HSL is going to be your friend. Especially if the hue argument is set up to take values from 0 to 360. And even better if these will wrap around, so 380 is really 20, and -30 is the same as 330.

For complementary, you take one color, convert it to HSV, add 180 to the H and create your second color.

For split-complementary, you take the original color, H + 150, and H + 210.

For triadic it’s original, H + 120, H + 240.

And tetradic is original, H + 90, H + 180, H + 270.

You can see these all start out with the same orangey color and have other colors added to them. In these cases, I’m just choosing a random palette entry for each triangle, so it gets a bit chaotic. Often the advice is to choose one or two colors as the main colors and use the others sparingly for contrast. So you might get things like…

In each case, I left the orange color as the contrast. The tetradic was the tougher one because all the colors differ significantly. But these are contrived examples anyway. In a real design you might have the background one color, foreground another. Maybe some text in a very contrasting color to pull your attention to it. Whatever.

A few more!

There’s an analogous palette where you take the original, H + 30, and H - 30.

And then there’s a spectral palette, which doesn’t mean ghostly, just that it spreads across a spectrum. In this case, hues of 60 to 120, which kind of does wind up a bit ghostly.

This is pretty similar to an analogous palette, but you can spread it out more than just 30 degrees and add as many colors as you want.

Finally, I wanted to make a pastel palette. This is quite similar to the spectral one, in that it spreads across a range of two hues, in this case 0 to 60, but it give each value a randomly low saturation.

Both of these last two use the same technique as monochromatic and achromatic - interpolating between the two hues n times, and adding a color with that hue to the palette.

Other palettes

Earlier I had a bit of a rant about the over-belief in these exact angles and combinations of colors for palettes, but they’re a decent rule of thumb. However, if you go onto one of the many color palette websites out there they’ll dump thousands of potential palettes on you, with fancy names like “Olive Garden Feast” or “Pastel Dreamland Adventure” (I did not make those up!) I’m sure most of these aren’t strictly related to any of the classic palette patterns above. They’re simply combinations of colors that people thought looked good. And yeah, most of them do look pretty good. Generally there are some similar colors and one or two contrasting ones. pal

So maybe I want to use one of these palettes in my library. Well often you can export a specific palette as a list of values. For example, https://coolors.co lets you copy a given palette as an array like so:

["#cdb4db","#ffc8dd","#ffafcc","#bde0fe","#a2d2ff"]

This, by the way is the infamous “Pastel Dreamland Adventure”!

On https://colorhunt.co you can also select and copy a palette pretty similarly. Other sites make it a bit more complicated but you can generally get a similar list, even if you have to copy and paste each value individually.

So I made a function where you can just pass a list of strings you can just paste in and get back a palette, like so:

palette = PaletteFromCSS("#cdb4db","#ffc8dd","#ffafcc","#bde0fe","#a2d2ff")

And Now I can have my own pastel dreamland adventure!

I also made functions that will save a given palette to disk in that exact format, and read a palette back in. So I have all those bases covered.

Palettes from images?

A oft-used creative way to come up with a palette is to sample colors from an image. So I did that. I made a function that you can pass a path to an image and a number of colors and it gives you back a palette, like so:

palette = SampleImagePalette("image.png", 5)

I grabbed this image (using my getimage app!) from pexels.com

Photo by Sergei Starostin: https://www.pexels.com/photo/vibrant-tropical-poke-bowl-with-green-smoothie-34902493/

And used that palette to create our usual triangle fill…

I’m not entirely satisfied with this function yet. I tried a bunch of different techniques. There are a lot of strategies to do this. Just randomly taking n number of colors from the image - but you might end up with very similar ones. I even did some work on measuring the difference between colors and making sure I didn’t have two that were too close together. Other ideas are color quantization - reducing the number of colors in the image, and creating a histogram and using colors from that. Nothing so far has really been great. So consider this a work in progress.

What I want to try is k-means clustering. You’d basically plot each color in a 3D space and use the k-means algorithm to find n clusters which should give you the main colors of the image. Watch for that some time this year, if I get around to it.

Summary

Well, I think I know a bit more about color now. And I definitely have created more than enough tools for myself to explore this area. So maybe I’ll be creating a few more colorful images now… maybe?

« Previous Post

Comments? Best way to shout at me is on Mastodon

Or share this post directly on Mastodon