Color

In this article we want to take a look at different color systems and spaces and how they work. How can we use them in our projects?

Subtractive color systems

Many have already wrapped their heads around colors and how they mix, behave together and can be systematically applied to artworks. For example Goethe's color theories or the color theories by Johannes Itten. These theories are all working in subtractive color systems. That means that they are based on pigments and how they mix together. It is called subtractive because pigments absorb certain wavelengths of light and reflect others. All colors mixed together create the color black.

Farbenkreis, aquarellierte Federzeichnung von Goethe, 1809, Original: Freies Deutsches Hochstift – Frankfurter Goethe-Museum
Farbenkreis, aquarellierte Federzeichnung von Goethe, 1809, Original: Freies Deutsches Hochstift – Frankfurter Goethe-Museum
Farbkreis nach Johannes Itten (1961)
Farbkreis nach Johannes Itten (1961)

Additive color systems

In comparison to the subtractive systems we have the additive colors systems. These are based on the physics of light. White light is the combination of all colors. We add colors to mix new ones. If we take all colors away we are left with black. These colors are nowadays digitally represented using the CMYK color space. This means that we use the colors cyan, magenta, yellow and black (key) to create new colors. A full CMYK black color coverage on our paper would mean we apply 400% of color. Normal papers are not able to absorb that much ink. In print we normally work with a black that has around 300% of color for images. The body text however is often printed with 100% of key (black) ink. So you can mix your black from CMY or add black ink and reduce one of the other colors. This is a design choice you will hae to make when working in print.

When working for newspapers you will have to use way less color. It all depends on the paper you are using.

Named Colors in CSS/p5js

To make our life a little easier we can use named colors in CSS or p5js. There are 147 named colors in CSS. Checkout colornames.inpyjamas.dev for all of them or one of the many other tools out there. E.g. htmlcolorcodes.com. All of these colors are in the RGB color space and are only an alias for color in the RGB color space.

RGB colors

The RGB color space is an additive color space. It is based on the light emitting diodes (LEDs) that are used in our screens. The RGB color space is based on the three primary colors red, green and blue. Each color can have a value between 0 and 255. Where 0 is the lowest intensity and 255 is the highest intensity. The color lightgoldenrodyellow for example, can also be represented as RGB color with the following values

RED GREEN BLUE
250 250 210

This gives us a color space with 256 x 256 x 256 = 16777216 different colors.

Hex colors

HEX colors, e.g. #ff6347 are just a different way to represent the RGB color space. Each color is represented by two hexadecimal digits. The first two digits represent the red color, the next two the green and the last two the blue. The digits can have values between 00 and FF. Hexadecimal is a base-16 numeral system. The calculation is 16 × 16 × 16 × 16 × 16 × 16 = 16777216. Which is the same as above for the RGB color space. See allhexcolors.inpyjamas.dev to kill some time and see all of these colors.

The # is not part of the color. It is just a prefix to indicate that the following is a HEX color.

The Problem with RGB Color Space

The problem with RGB color space is that it is not a intuitive space. It is hard to understand how to mix colors together to get the color we want and also how to create harmonic color palettes based on an existing color. This is where the HSB color space comes into play.

RGB Cube by Datumizer Wikimedia

HSB (HSV)

The HSB color space is based on the color wheel. It is a cylindrical coordinate system. The color is represented by three values:

HSV_color_solid_cylinder.png: SharkDderivative work: SharkD  Talk

HSV color solid_cylinder.png: SharkD derivative work: SharkD  Talk, CC BY-SA 3.0, via Wikimedia Commons

This color system/space allows us to intuitively select colors and also create colors based on a rule set.

HSL

The HSL color space is very similar to the HSB color space. One difference is that the brightness from HSB is represented here as lightness.

HSL color solid_cylinder.png: SharkD derivative work: SharkD  Talk, CC BY-SA 3.0, via Wikimedia Commons

HSL HSB comparison

So what is the difference? One difference is that in HSL to create black or white we just have to change the lightness to 0 or 100. In HSB we would have to set the saturation to 0 and the brightness to 100 to create white. To create black in HSB we would have to set the saturation to 0 and the brightness to 0. Keeping the saturation at 100 would create a bright color of that hue we picked.

The problem with HSB/HSL

Of course there is always room for improvement. The HSB/HSL color spaces are purely based on the color wheel and mathematical calculations. They do not take into account how humans perceive colors.

As an example. In the image below we use exactly the same color saturation and brightness in HSB color space. But the green we see "feels" way more intense than the violet.

function setup() {
		createCanvas(200, 100);
		colorMode(HSB);
		noStroke();
		const gutter = width / 20;
		const c1 = color(100, 100, 100);
		fill(c1);
		rect(gutter, gutter, width / 2 ‐ gutter * 2, height ‐ gutter * 2);
		const c2 = color(hue(c1) + 180, 100, 100);
		fill(c2);
		rect(width / 2 + gutter, gutter, width / 2 ‐ gutter * 2, height ‐ gutter * 2);
		}
	function draw() {}

To overcome these problems there are other color spaces that take into account how humans perceive colors. The latest addition to the CSS is okLCH color space which is a cylindrical coordinate system for the okLAB color space. Below you a comparison of the HSV color space and the OKLCH color space. The colors in the okLCH color space are more harmonious.

OKLCH color space

By Intervex - Own work, CC0, Link

Colors in p5js

In the default RGB color space we can create and use colors in several ways. To create a color from white over gray to black we can use the following code.

const c = color(128);
	

If we want to give our color an alpha value we can do so by adding an additional value.

const c = color(128, 100);
	

To create a color from the RGB color space we can use the following code (in this case red).

const c = color(255, 0, 0);
	

To create a color from the RGB color space with an alpha value we can use the following code.

const c = color(255, 0, 0, 100);
	

Colors in HSB/HSL

Since we are working in p5js we can not make use of such color like okLCH without using a specialized library. To not introduce any new dependencies we will keep using the HSB or HSL color space that comes with p5js.

To use the HSB colors we have to use the colorMode(HSB) function.

pink square h330 s70 b90
function setup() {
		createCanvas(100, 100);
		colorMode(HSB);
		background(330, 70, 50);
	}

The same code in HSL color space.

pink square h330 s70 b90
function setup() {
		createCanvas(100, 100);
		colorMode(HSL);
		background(330, 70, 50);
		}

The colorMode function allows us not only the switch between HSB, HSL and RGB (the default) but also allows to change the value ranges we are working with.

Below we define the ranges for hue to be between 0 and 360, for saturation and brightness to be between 0 and 100 and the last value for alpha to be between 0 and 100.

colorMode(HSB, 360, 100, 100, 100);
	

Color Rulesets

Using the color wheel we can create different color rulesets.

Monochromatic

A monochromatic color ruleset is a set of colors that are different shades of the same color.

Saturation

monochromatic saturation
function setup() {
		createCanvas(100, 100);
		colorMode(HSB, 360, 100, 100, 100);
		background("white");
		const angle = 330;
		const numberOfColor = 5;

		let sat = 0;
		let brght = 90;
		let satStep = 100 / numberOfColor;
		let rectWidth = width / numberOfColor;
		let x = 0;
		const y = 0;
		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);

		x = x + rectWidth;
		sat = sat + satStep;

		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);

		x = x + rectWidth;
		sat = sat + satStep;

		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);

		x = x + rectWidth;
		sat = sat + satStep;

		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);

		x = x + rectWidth;
		sat = sat + satStep;

		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);
		// loop loop loop loop me 😱!
	}

	function draw() {}

Brightness

monochromatic brightness
function setup() {
		createCanvas(100, 100);
		colorMode(HSB, 360, 100, 100, 100);
		background("white");
		const angle = 330;
		const numberOfColor = 5;

		let sat = 50;
		let brght = 0;
		let brghtStep = 100 / numberOfColor;
		let rectWidth = width / numberOfColor;
		let x = 0;
		const y = 0;
		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);

		x = x + rectWidth;
		brght = brght + brghtStep;

		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);

		x = x + rectWidth;
		brght = brght + brghtStep;

		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);

		x = x + rectWidth;
		brght = brght + brghtStep;

		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);

		x = x + rectWidth;
		brght = brght + brghtStep;

		fill(angle, sat, brght);
		rect(x, y, rectWidth, height);
		// also pleeeeease loop me 😱
	}

	function draw() {}

Analogous

An analogous color ruleset is a set of colors that are next to each other on the color wheel. Here we do a step of 20 degrees.

analogous color ruleset
const numberOfRects = 5;
	function setup() {
		createCanvas(100, 100);
		colorMode(HSB, 360, 100, 100, 100);
		background("grey");
		noStroke();
		let x = 0;
		let y = 0;
		const rectWidth = width / numberOfRects;
		const hueStep = 20;

		let h = 330;
		fill(h, 70, 100);
		rect(x, y, rectWidth, height);

		x += rectWidth;
		h ‐= hueStep;

		fill(h, 70, 100);
		rect(x, y, rectWidth, height);

		x += rectWidth;
		h ‐= hueStep;

		fill(h, 70, 100);
		rect(x, y, rectWidth, height);

		x += rectWidth;
		h ‐= hueStep;

		fill(h, 70, 100);
		rect(x, y, rectWidth, height);

		x += rectWidth;
		h ‐= hueStep;

		fill(h, 70, 100);
		rect(x, y, rectWidth, height);
		// this cries for a loop 😭
	}
	function draw() {}

Complementary

A complementary color ruleset is a set of colors that are opposite each other on the color wheel.

complementary color ruleset
function setup() {
		createCanvas(100, 100);
		noStroke();
		colorMode(HSB, 360, 100, 100, 100);
		let angle = 330;
		let x = 0;
		const y = 0;

		fill(angle, 70, 100);
		rect(x, y, width / 2, height);

		angle += 180;
		x += width / 2;

		fill(angle % 360, 70, 100);
		rect(x, y, width / 2, height);
	}

	function draw() {}
	

Triadic

A triadic color ruleset is a set of colors that are three colors evenly spaced on the color wheel. Here we do a step of 120 degrees.

triadic color ruleset
let angle = 330;
	function setup() {
		createCanvas(100, 100);
		colorMode(HSB, 360, 100, 100, 100);
		noStroke();
		background("white");
		let x = 0;
		let y = 0;

		const rectWidth = width / 3;
		fill(angle, 50, 70);
		console.log({ angle });
		rect(x, y, rectWidth, height);

		x += rectWidth;
		angle = angle ‐ 120;
		console.log({ angle });

		fill(angle, 50, 70);
		rect(x, y, rectWidth, height);
		x += rectWidth;
		angle = angle ‐ 120;
		console.log({ angle });

		fill(angle, 50, 70);
		rect(x, y, rectWidth, height);
		// this screams for a loop 🙀
		// what happens below 0?
	}
	function draw() {}
	

Split Complementary

(to be defined)

Tetradic

A tetradic color ruleset is a set of colors that are four colors evenly spaced on the color wheel. Here we do a step of 90 degrees.

tetradic color ruleset
function setup() {
		createCanvas(100, 100);
		noStroke();
		colorMode(HSB, 360, 100, 100, 100);
		let angle = 330;
		const numberOfColors = 4;
		const incr = 90;

		const rectWidth = width / (numberOfColors / 2);
		const rectHeight = height / (numberOfColors / 2);

		for (let x = 0; x < width; x = x + rectWidth) {
		for (let y = 0; y < height; y = y + rectHeight) {
		fill(angle % 360, 70, 100);
		rect(x, y, rectWidth, height);
		angle += incr;
		}
		}
		// finally a loop!YAY\o/!!!!1!
	}

	function draw() {}
	

Double Complementary

(to be defined)

Tools

Libraries

MacOS

Windows

Online