noise grid
May 13, 2022

Balanced Color Palettes — Generative Snacks!

Hey! Welcome to ✨ Generative Snacks ✨, a collection of tiny-yet-delicious generative art tips and tricks. 🍪

Color is a powerful, yet potentially troublesome component in most generative art and design. A carefully applied palette can elevate an “OK” composition to new heights, while an unpolished, chaotic set of colors can quickly ruin a great one.

Frequently, us artists and designers discover a set of colors we love but struggle to apply them to a piece. Why? Our first instinct is often to distribute color at random throughout our canvas — while this can look wonderful, it can also feel visually overwhelming and unstable.

Here’s a technique I love to use to create balanced color palettes in my work.

The core function

The core part of this technique is the createWeightedSelector function:

function createWeightedSelector(items) {
const weightedArray = [];

for (const item of items) {
for (let i = 0; i < item.weight; i++) {
weightedArray.push(item.value);
}
}

return function () {
return weightedArray[Math.floor(Math.random() * weightedArray.length)];
};
}

This function takes an array of objects, each with a value and weight property. The value property can be anything — a color, string, number, etc. The weight property is how likely that value is to be selected. A higher weight increases the chance of selection:

const items = [
{
weight: 50,
value: 'tomato',
},
{
weight: 25,
value: 'orange',
},
];

When called, createWeightedSelector returns a new function. This function will produce a random value each time it is run, based on the criteria defined above:

const pickColor = createWeightedSelector(items);

const color = pickColor();

Crafting balanced color distributions

Here’s how we could use createWeightedSelector to create a color composition that follows the 60 / 30 / 10 rule:

const pickColor = createWeightedSelector([
{
weight: 60, // 60% chance of being picked
value: 'black',
},
{
weight: 30, // 30% chance of being picked
value: 'orange',
},
{
weight: 10, // 10% chance of being picked
value: 'tomato',
},
]);

Here, our neutral color has a 60% chance of being picked, our primary color has a 30% chance, and our complementary color has a 10% chance.

Each time we render an object to our canvas, we use the function returned by createWeightedSelector to decide its color. In doing so, we naturally create a balanced color distribution across our composition!

const color = pickColor();

renderObject(color);

function renderObject(color) {
// ...
}

Note: the weights passed to createWeightedSelector are arbitrary and do not need to add up to 100.

An example

Here is a CodePen demonstrating the difference between a weighted color distribution and a purely random one. Try toggling the weighting on and off and see how the composition changes — to my eye, becoming more or less chaotic.

As with many great generative techniques, the difference is subtle, and that’s perfect. The key to great generative work is lots of light touches. You could try combining weighted color palettes with a touch of color modulation, for example, to craft some super tasteful compositions.

Until next time!