How to compute visually distinct CSS hues with the same general brightness

62 Views Asked by At

I wanted to get multiple colors that have the same perceptual brightness but distinct hues.

I understand that CSS's hsl color space does not have a consistent perceptual brightness as you vary the hue

enter image description here

above, lines 0, 5, and 6 are clearly much darker than lines 1, 2, 3, 4.

So, I tried, lch color which supposedly has perceptually consistent brightness.

Unfortunately, unless I'm color blind, it does NOT have perceptually distinct hues for a given distance

Here the distance between hues is 45 degrees

enter image description here

At least to my eyes, lines 4, 5, and 6 appear identical even though they are 45 degrees apart

Here's some code I was using to experiment

const hsl = (h, s, l) => `hsl(${h * 360}, ${s * 100}%, ${l * 100}%)`;
const hwb = (h, w, b) => `hwb(${h * 360} ${w * 100}% ${b * 100}%)`;
const lch = (l, c, h) => `lch(${l * 100} ${c * 250 - 125} ${h * 360})`;
const range = (n, fn) => new Array(n).fill(0).map((_, i) => fn(i)); // eww

// creates 'num' div
// fn is a function that receives a number between 0 and 1
// it should return a CSS color
function addDivs(num, fn, label) {
  document.body.appendChild(el('div', { className: 'group' }, [
    el('h2', {textContent: label}),
    el('div', {className: 'colors'}, range(num, i => el('div', {
      style: {
        backgroundColor: fn(i / num),
      },
      textContent: `${i}: ${fn(i / num)}`,
    }))),
  ]));
}

//addDivs(8, l => lch(l, 1, 0), "lch(l)");
//addDivs(8, c => lch(1, c, 0), "lch(c)");
addDivs(8, h => lch(1, 1, h), "lch(1, 1, h)");
addDivs(8, h => hsl(h, 1, 0.5), "hsl(h, 1, 0.5)");
addDivs(8, h => hwb(h, 0, 0), "hwb(h, 0, 0)");

addDivs(8, h => lch(0.7, 0.8, h), "lch(0.7, 0.8, h)");


function el(tag, attrs = {}, children = []) { 
  const elem = document.createElement(tag);
  for (const [key, value] of Object.entries(attrs)) {
    if (typeof value === 'function' && key.startsWith('on')) {
      const eventName = key.substring(2).toLowerCase();
      elem.addEventListener(eventName, value, {passive: false});
    } else if (typeof value === 'object') {
      for (const [k, v] of Object.entries(value)) {
        elem[key][k] = v;
      }
    } else if (elem[key] === undefined) {
      elem.setAttribute(key, value);
    } else {
      elem[key] = value;
    }
  }
  for (const child of children) {
    elem.appendChild(child);
  }
  return elem;
}

I notice if I lower the c chroma value below say 0.8 then I get perceptually different hues for at the same brightness. Unfortunately the hues are no longer saturated.

Is there some other color space or calculation that will give me distinct saturated colors for the same distances between hues?

0

There are 0 best solutions below