All posts

colour

2 posts

p5.js Session 13: Colour, Hex and Palettes

Session 12 covered RGB and HSB, two ways of describing a colour to p5.js. There is a third, and it is the one you will meet most often outside the editor.

Hex: one more way to write a colour

A hex code looks like #0077B6. The hash is followed by six characters, read as three pairs: 00 for red, 77 for green, B6 for blue. Each pair is one channel written in base 16, which counts 0 to 9 then A to F, so FF is 255 and 00 is 0. It is the same RGB colour, written shorter.

p5.js takes a hex code as a string, quotes included, anywhere it takes a colour. Here is one blue three times over, written three different ways.

function setup() {
  createCanvas(600, 300);
  noLoop();
  noStroke();
}
function draw() {
  background(0);

  // RGB: red, green, blue, each 0 to 255
  fill(0, 119, 182);
  circle(150, 150, 140);

  // Hex: the same colour as a string, three pairs after the hash
  fill('#0077B6');
  circle(300, 150, 140);

  // HSB: hue, saturation, brightness
  colorMode(HSB, 360, 100, 100);
  fill(200.8, 100, 71.4);
  circle(450, 150, 140);
}

Three identical blue circles on black, the same colour written as RGB, as hex, and as HSB

  • fill(0, 119, 182) and fill('#0077B6') are the same colour. 77 in hex is 119, B6 is 182.
  • The quotes matter. '#0077B6' is a string, and p5.js reads a string as a colour. Without them it is not valid code.
  • The third circle switches over with colorMode(HSB, 360, 100, 100), then names that same blue as hue 200.8, saturation 100, brightness 71.4.
  • The decimals are not fussiness. Converting a colour into HSB rarely lands on whole numbers, and rounding them would leave the third circle a shade off the other two.

Hex is worth knowing because it is what the rest of the world uses. Design tools export it, CSS uses it, and every palette site hands you a row of hex codes ready to paste.

Gathering colours into a palette

Generative art rarely picks colours one at a time. It works from a chosen set, and holding that set in an array means you write the codes once and refer to them by position afterwards.

The nine below are Ocean Blue Serenity, taken from the trending palettes on Coolors, which is where I go when I want a set of colours that already agree with each other.

One script does both halves of the job. setup() creates the palette, draw() uses it, one swatch per colour.

let palette;
function setup() {
  createCanvas(650, 350);
  noLoop();
  noStroke();
  palette = [
    '#03045E', '#023E8A', '#0077B6', '#0096C7', '#00B4D8',
    '#48CAE4', '#90E0EF', '#ADE8F4', '#CAF0F8'
  ];
}
function draw() {
  background(0);
  for (let i = 0; i < palette.length; i++) {
    fill(palette[i]);
    rect((i * 70) +10, 50, 70, 250);
  }
}

Nine swatches in a row on black, the Ocean Blue Serenity palette from darkest navy to palest blue

  • let palette; sits outside setup(), so every function in the sketch can see it.
  • Filling it inside setup() means it is built once, when the sketch starts, not on every frame.
  • Square brackets make an array. The nine hex codes go in as strings, separated by commas.
  • palette[0] is the first colour and palette[8] the last, because arrays count from 0.
  • palette[i] in the loop is the using half. Swatch one takes colour one, swatch two takes colour two, straight through to the ninth.
  • palette.length is 9, so the loop covers the whole palette without you counting anything.

A circle divided among the palette

A palette of nine and a circle of 360 degrees fit together neatly: nine wedges of 40 degrees each, one colour per wedge.

let palette;
function setup() {
  createCanvas(600, 600);
  angleMode(DEGREES);
  noLoop();
  noStroke();
  palette = [
    '#03045E', '#023E8A', '#0077B6', '#0096C7', '#00B4D8',
    '#48CAE4', '#90E0EF', '#ADE8F4', '#CAF0F8'
  ];
}
function draw() {
  background(0);
  let wedgeAngle = 360 / palette.length;
  for (let i = 0; i < palette.length; i++) {
    fill(palette[i]);
    arc(300, 300, 400, 400, i * wedgeAngle, (i + 1) * wedgeAngle, PIE);
  }
}

A circle divided into nine equal wedges on black, each wedge a colour from the Ocean Blue Serenity palette

  • arc(x, y, w, h, start, stop) draws a slice of an ellipse, set by the two angles.
  • PIE closes that slice back to the centre, which is what makes a wedge rather than a curved line.
  • angleMode(DEGREES), from Session 7, lets the angles be written as 0 to 360 instead of radians.
  • wedgeAngle is 360 / palette.length, so the wedges divide the circle evenly whatever size the palette is. Nine colours give 40 degrees each.
  • Wedge i runs from i * wedgeAngle to (i + 1) * wedgeAngle, so each one starts exactly where the last finished.

Nine colours, nine wedges, the whole palette visible at once. Change one hex code and one slice of the circle changes with it.

The scripts in this session were written for p5.js 2.3.2, the version the web editor runs by default.


Next session, we get a sketch out of the browser: saving your work as an image file.

p5.js Session 12: Colour, HSB and RGB

In Session 11 we built our own shapes with vertex() and splineVertex(). This session leaves shape aside and looks at colour itself.

RGB, a recap

Every colour we’ve used so far has been RGB: red, green, and blue, each from 0 to 255. Mixing the three gives you any colour on screen, but picking a specific hue this way takes some guessing, there’s no single number that means “orange.”

HSB: hue, saturation, brightness

HSB describes colour differently: hue is the colour itself, one number around a wheel from 0 to 360. Saturation is how vivid it is, 0 is grey, 100 is fully saturated. Brightness is how light or dark it is. colorMode(HSB) switches p5.js over to reading colours this way.

Let’s put twelve circles in a row and give each one a hue a little further around the wheel than the last.

function setup() {
  createCanvas(650, 200);
  colorMode(HSB, 360, 100, 100);
  noLoop();
  noStroke();
}
function draw() {
  background(0, 0, 0);
  for (let i = 0; i < 12; i++) {
    let hueValue = i * 30;
    fill(hueValue, 80, 90);
    circle(50 + i * 50, 100, 40);
  }
}

A row of 12 circles shifting through the full colour spectrum using HSB

  • colorMode(HSB, 360, 100, 100) sets the scale for each value: hue up to 360, saturation and brightness up to 100.
  • hueValue climbs by 30 on every pass of the loop, so twelve circles cover the whole wheel.
  • Saturation and brightness stay fixed at 80 and 90, so nothing changes from circle to circle except the colour itself.

This is why HSB suits generative art so well: cycling through colour is just counting, rotating a single number around the wheel, instead of juggling three RGB values at once.

Opacity: the alpha channel

Both RGB and HSB accept a fourth number in fill(): alpha, or opacity. 0 is fully invisible, the maximum (255 in RGB, 100 in HSB) is fully opaque.

Two overlapping circles, neither of them solid, show what that actually does.

function setup() {
  createCanvas(600, 600);
  noLoop();
  noStroke();
}
function draw() {
  background(0);
  fill(200, 50, 50, 150);
  circle(240, 300, 300);
  fill(50, 100, 200, 150);
  circle(360, 300, 300);
}

Two overlapping circles, red and blue, both partially transparent, blending into a third colour where they meet

  • The fourth number in fill() is the alpha. Both circles use 150 out of 255, so both are translucent rather than solid.
  • Where the two circles overlap, the red and the blue blend into a third colour.
  • The black background shows faintly through both, because neither one covers it completely.

Alpha works exactly the same way in colorMode(HSB), just add it as the fourth value there too.

The scripts in this session were written for p5.js 2.3.2, the version the web editor runs by default.


Next session, we take colour further: building a reusable palette, stored in an array, instead of picking values one at a time.