JavaScript Calculate brighter colour

Viewed 35506

I have a colour value in JS as a string

#ff0000

How would I go about programatically calculating a brighter/lighter version of this colour, for example #ff4848, and be able to calculate the brightness via a percentage, e.g.

increase_brightness('#ff0000', 50); // would make it 50% brighter
12 Answers

I found a variation of Sanghyun Lee's reply generates the best result.

  1. Convert RGB to HSL
  2. Set the brightness of HSL
  3. Convert back from HSLto RGB

The difference/variation is how you increase/decrease the brightness.

newBrightness = HSL[2] + HSL[2] * (percent / 100) // Original code

Instead of applying a percentage on the current brightness, it works better if it is treated as absolute increment/decrement. Since the luminosity range is 0 to 1, the percent can be applied on the whole range (1 - 0) * percent/100.

newBrightness = HSL[2] + (percent / 100);
newBrightness = Math.max(0, Math.min(1, newBrightness));

Another nice property of this approach is increment & decrement negate each other.

Image below shows darker and lighter colors with 5% increment. Note, how the palette is reasonably smooth and often ends with black and white.

Color Palette

Palette with original approach - gets stuck at certain colors.

enter image description here

I know this an old question, but I found no answer that simply manipulates css hsl color. I found the old answers here to be too complex and slow, even producing poor results, so a different approach seems warranted. The following alternative is much more performant and less complex.

Of course, this answer requires you to use hsl colors throughout your app, otherwise you still have to do a bunch of conversions! Though, if you need to manipulate brightness eg in a game loop, you should be using hsl values anyway as they are much better suited for programmatic manipulation. The only drawback with hsl from rgb as far as I can tell, is that it's harder to "read" what hue you're seeing like you can with rgb strings.

function toHslArray(hslCss) {
    let sep = hslCss.indexOf(",") > -1 ? "," : " "
    return hslCss.substr(4).split(")")[0].split(sep)
}

function adjustHslBrightness(color, percent) {
    let hsl = toHslArray(color)
    return "hsl(" + hsl[0] + "," + hsl[1] + ", " + (percent + "%") + ")"
}

let hsl = "hsl(200, 40%, 40%)"
let hsl2 = adjustHslBrightness(hsl, 80)

function brighten(color, c) {
  const calc = (sub1,sub2)=> Math.min(255,Math.floor(parseInt(color.substr(sub1,sub2),16)*c)).toString(16).padStart(2,"0")
  return `#${calc(1,2)}${calc(3,2)}${calc(5,2)}`
}

const res = brighten("#23DA4C", .5) // "#116d26"
console.log(res)

A variant with lodash:

// color('#EBEDF0', 30)
color(hex, percent) {
  return '#' + _(hex.replace('#', '')).chunk(2)
    .map(v => parseInt(v.join(''), 16))
    .map(v => ((0 | (1 << 8) + v + (256 - v) * percent / 100).toString(16))
    .substr(1)).join('');
}

What I use:

//hex can be string or number
//rate: 1 keeps the color same. < 1 darken. > 1 lighten.
//to_string: set to true if you want the return value in string
function change_brightness(hex, rate, to_string = false) {
    if (typeof hex === 'string') {
        hex = hex.replace(/^\s*#|\s*$/g, '');
    } else {
        hex = hex.toString(16);
    }
    if (hex.length == 3) {
        hex = hex.replace(/(.)/g, '$1$1');
    } else {
        hex = ("000000" + hex).slice(-6);
    }
    let r = parseInt(hex.substr(0, 2), 16);
    let g = parseInt(hex.substr(2, 2), 16);
    let b = parseInt(hex.substr(4, 2), 16);

    let h, s, v;
    [h, s, v] = rgb2hsv(r, g, b);
    v = parseInt(v * rate);
    [r, g, b] = hsv2rgb(h, s, v);

    hex = ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
    if (to_string) return "#" + hex;
    return parseInt(hex, 16);
}

function rgb2hsv(r,g,b) {
    let v = Math.max(r,g,b), n = v-Math.min(r,g,b);
    let h = n && ((v === r) ? (g-b)/n : ((v === g) ? 2+(b-r)/n : 4+(r-g)/n)); 
    return [60*(h<0?h+6:h), v&&n/v, v];
}

function hsv2rgb(h,s,v) {
    let f = (n,k=(n+h/60)%6) => v - v*s*Math.max( Math.min(k,4-k,1), 0);
    return [f(5),f(3),f(1)];
}
Related