Perceptual Brightness Preview

by Ben Gillbanks

HTML

<h1>Perceptual Brightness Viewer</h1>
<p>See original colors and their perceptual equivalents.</p>
<p>Perceptual brightness is how dark colours appear to the eye, as opposed to how bright they are mathematically (when described with hsl for example).</p>
<div class="palette" id="palette"></div>

CSS

body {
    font-family: Arial, sans-serif;
    padding: 20px;
    line-height: 1.6;
}
h1 { 
    line-height: 1.1; 
}
.palette {
    display: flex;
}
.swatch {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex-grow:1;
    margin-bottom: 10px;
}
.color-box {
    width: 100%;
    height: 80px;
}

JavaScript

// Input colors
const colors = [
    "#0A0C1F", // Very dark blue - almost black. The darkest colour.
    "#263264", // Dark blue
    "#A0ABB6", // Mid grey
    "#B2EFEB", // Light blue
    "#3FB0F1", // Mid blue
    "#3548A3", // Blue
    "#420241", // Dark red/ purple
    "#6A3E49", // Brown
    "#C22D44", // Red
    "#E08355", // Orange
    "#FFC763", // Yellow
    "#A7D171", // Light green
    "#30AB62", // Green
    "#1E7F82", // Dark Green
    "#FF76D7", // Pink
    "#F4F4F4", // Off white
];


// Constants for sRGB luminance
const rY = 0.212655;
const gY = 0.715158;
const bY = 0.072187;

// Inverse of sRGB "gamma" function
function invGamSRGB(ic) {
    const c = ic / 255.0;
    return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
}

// sRGB "gamma" function
function gamSRGB(v) {
    v = v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1.0 / 2.4) - 0.055;
    return Math.round(v * 255);
}

// Perceptual brightness (gray value)
function perceptualBrightness(r, g, b) {
    const luminance = rY * invGamSRGB(r) + gY * invGamSRGB(g) + bY * invGamSRGB(b);
    return gamSRGB(luminance);
}

// Convert hex to RGB
function hexToRGB(hex) {
    const bigint = parseInt(hex.slice(1), 16);
    return {
        r: (bigint >> 16) & 255,
        g: (bigint >> 8) & 255,
        b: bigint & 255,
    };
}

// Convert RGB to hex
function rgbToHex({ r, g, b }) {
    return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
}

// Generate swatches
function generateSwatches(colors) {
    const palette = document.getElementById("palette");

    // Sort colors by perceptual brightness
    const sortedColors = colors.map((color) => {
        const rgb = hexToRGB(color);
        const brightness = perceptualBrightness(rgb.r, rgb.g, rgb.b);
        return { color, brightness };
    }).sort((a, b) => a.brightness - b.brightness);

    sortedColors.forEach(({ color }) => {
        const rgb = hexToRGB(color);
        const perceptualRGB = {
            r:...