47

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
hlt
  • 5,916
  • 2
  • 19
  • 39
Ozzy
  • 9,483
  • 25
  • 86
  • 132
  • 2
    If you want to be able to reduce the brightness again after one or more color channels are saturated, remember to store the original color! – hughes Jun 22 '11 at 17:36

12 Answers12

86
function increase_brightness(hex, percent){
    // strip the leading # if it's there
    hex = hex.replace(/^\s*#|\s*$/g, '');

    // convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF`
    if(hex.length == 3){
        hex = hex.replace(/(.)/g, '$1$1');
    }

    var r = parseInt(hex.substr(0, 2), 16),
        g = parseInt(hex.substr(2, 2), 16),
        b = parseInt(hex.substr(4, 2), 16);

    return '#' +
       ((0|(1<<8) + r + (256 - r) * percent / 100).toString(16)).substr(1) +
       ((0|(1<<8) + g + (256 - g) * percent / 100).toString(16)).substr(1) +
       ((0|(1<<8) + b + (256 - b) * percent / 100).toString(16)).substr(1);
}

/**
 * ('#000000', 50) --> #808080
 * ('#EEEEEE', 25) --> #F2F2F2
 * ('EEE     , 25) --> #F2F2F2
 **/
Mark Kahn
  • 81,115
  • 25
  • 161
  • 212
  • I've never worked with JavaScript's bitwise operators or really bitwise operators in any language. I had seen them posted in solutions on SO before, but I decided to google them today to understand what they're for and what they're doing. I understand now they're for modifying the binary representations of numbers. I imagine it allows you to do what would otherwise be tricky calculations. My question is, why the need for (1<<8) in your solution? As those are both static value, would it not be simpler to just write 256? – WesleyJohnson Jun 22 '11 at 20:15
  • 7
    @WesleyJohnson - Yes, I could just as easily write `256`, but the ___function___ of this is to get the 9th bit to be a 1, so `1<<8` makes logical sense. Basically without that piece, if the rest of the calculation came out to `5`, you would end up with `(5).toString(16)` which is just `5`. Since I need it to be 2 characters long, the `1<<8` makes it `105` and I can chop the 1 to get `05`. Now the reason it's `1<<8` instead of `256`: If I needed it to be 4 characters long I could type `1<<16`, `1<<24` for 6 characters, `(1<<24)*(1<<8)` for 8, etc... – Mark Kahn Jun 22 '11 at 20:49
  • 4
    and FYI: the `0|` is to floor the value. It's the functional equivalent of `Math.floor(...)` – Mark Kahn Jun 22 '11 at 20:54
  • I really appreciate the explanation. I'm gonna have to study the code and play around with those operators and concepts for a bit..... pun intended. :P – WesleyJohnson Jun 22 '11 at 20:57
  • Great Work, many thanks! Is there any chance, something similar could be done for Hue or HSB-like transformations? – SunnyRed Jul 01 '13 at 19:56
  • @SunnyRed -- I may be wrong on this, but I think you can just push S towards 0 and B towards 100 to "lighten", and flip that to "darken". e.g. "lighten by 10%" would just be `S/=1-.1; B*=1-.1` – Mark Kahn Jul 02 '13 at 03:38
  • @cwolves: Thanks, but I meant in a way, that when I increase the hue of say `#FF0000` by 30° it would result in `#FF7F00`. I found the following, but I am still unable to solve the puzzle http://stackoverflow.com/questions/4106363/converting-rgb-to-hsb-colors – SunnyRed Jul 02 '13 at 16:30
  • With some browsing and hacking, I was able to answer my own question. In case it is of interest: http://stackoverflow.com/questions/17433015/change-the-hue-of-a-rgb-color-in-javascript – SunnyRed Jul 02 '13 at 18:28
  • Is it ok to edit answer to `((r + (256 - r) * percent / 100) & 255).toString(16)`? – TotalAMD Aug 29 '18 at 09:22
  • @TotalAMD -- That's not doing the same thing. Specifically `256 +` vs `255 &`. You won't get correct results with that proposed change – Mark Kahn Aug 30 '18 at 14:28
  • Hey! This will be better, if you can give the example of decrease_brightness – Kunvar Singh Jan 11 '20 at 09:08
19

Update

@zyklus's answer is simpler and has the same effect. Please refer to this answer only if you are interested in converting between RGB and HSL.


To set the brightness of RGB:

  1. Convert RGB to HSL

  2. Set the brightness of HSL

  3. Convert back from HSL to RGB

This link used to have code to convert RGB to HSL and reverse: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript

/**
 * Converts an RGB color value to HSL. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes r, g, and b are contained in the set [0, 255] and
 * returns h, s, and l in the set [0, 1].
 *
 * @param   Number  r       The red color value
 * @param   Number  g       The green color value
 * @param   Number  b       The blue color value
 * @return  Array           The HSL representation
 */
function rgbToHsl(r, g, b){
    r /= 255, g /= 255, b /= 255;
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;

    if(max == min){
        h = s = 0; // achromatic
    }else{
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch(max){
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }

    return [h, s, l];
}

/**
 * Converts an HSL color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes h, s, and l are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 255].
 *
 * @param   Number  h       The hue
 * @param   Number  s       The saturation
 * @param   Number  l       The lightness
 * @return  Array           The RGB representation
 */
function hslToRgb(h, s, l){
    var r, g, b;

    if(s == 0){
        r = g = b = l; // achromatic
    }else{
        function hue2rgb(p, q, t){
            if(t < 0) t += 1;
            if(t > 1) t -= 1;
            if(t < 1/6) return p + (q - p) * 6 * t;
            if(t < 1/2) return q;
            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        }

        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
    }

    return [r * 255, g * 255, b * 255];
}

I made some example with it. Check this link: http://jsfiddle.net/sangdol/euSLy/4/

And this is the increase_brightness() function:

function increase_brightness(rgbcode, percent) {
    var r = parseInt(rgbcode.slice(1, 3), 16),
        g = parseInt(rgbcode.slice(3, 5), 16),
        b = parseInt(rgbcode.slice(5, 7), 16),
        HSL = rgbToHsl(r, g, b),
        newBrightness = HSL[2] + HSL[2] * (percent / 100), 
        RGB;

    RGB = hslToRgb(HSL[0], HSL[1], newBrightness);
    rgbcode = '#'
        + convertToTwoDigitHexCodeFromDecimal(RGB[0])
        + convertToTwoDigitHexCodeFromDecimal(RGB[1])
        + convertToTwoDigitHexCodeFromDecimal(RGB[2]);

    return rgbcode;
}

function convertToTwoDigitHexCodeFromDecimal(decimal){
    var code = Math.round(decimal).toString(16);

    (code.length > 1) || (code = '0' + code);
    return code;
}

You can pass a negative value as a percent argument to make it darken.

Sanghyun Lee
  • 17,726
  • 18
  • 88
  • 118
  • nice solution, but very overly complicated. You can do the entire thing in RGB without flipping back and forth. – Mark Kahn Jun 22 '11 at 21:05
  • @cwolves Yes, it's complicated. However, this solution set the real brightness of color. I think your solution is simple but it just calculate the number of RGB code, and I don't think that number increase in same ratio with brightness. – Sanghyun Lee Jun 23 '11 at 00:43
  • @Sangdol - it's actually identical if you look at the numbers it generates. The exception is that you also have a "darken" function built-in by controlling the saturation from a 0-100 value. Basically your "51-100" is a "lighten" that functions identical to my code. If you flip a few numbers in my code you can get a darken that is your "0-49" – Mark Kahn Jun 23 '11 at 05:27
  • @cwolves Yes, I found it is identical, and as you said, yours can't make it darken. But much simpler. :) I found some threads related to this question. http://stackoverflow.com/questions/5484389/how-to-modify-color-brightness-in-c http://stackoverflow.com/questions/737217/how-do-i-adjust-the-brightness-of-a-color – Sanghyun Lee Jun 23 '11 at 14:30
  • 3
    @Sangdol - well to darken it you would simply replace the `r + (256 - r) * percent / 100` piece with `r * (100 - percent) / 100`. As I said, there is nothing wrong with your solution, but it's simply a waste to do the RGB->HSL->RGB conversions. Luminosity is, simply put, how close a color is to white or black. So to make something 50% brighter, you simply split the difference between the color and white. Fairly simple logic :) – Mark Kahn Jun 23 '11 at 16:49
  • @cwolves Ok, I see. I admit that yours is better :) – Sanghyun Lee Jun 24 '11 at 16:41
  • This is the way to go. Alas, due to link rot, mjijackson.com is no longer accessible (thankfully jsfiddle.net is). I suggest changing the comment to make it self-contained. – Aaa Jul 13 '14 at 10:20
4

In case anyone needs it, I converted the color brightness JavaScript code to ASP / VBScript for a project and thought I would share it with you:

'::Color Brightness (0-100)
'ex.     ColorBrightness("#FF0000",25)  'Darker
'ex.     ColorBrightness("#FF0000",50)  'Mid
'ex.     ColorBrightness("#FF0000",75)  'Lighter
Function ColorBrightness(strRGB,intBrite)
    strRGB = Replace(strRGB,"#","")

    r = CInt("&h" & Mid(strRGB,1,2))
    g = CInt("&h" & Mid(strRGB,3,2))
    b = CInt("&h" & Mid(strRGB,5,2))

    arrHSL = RGBtoHSL(r, g, b)
    dblOrigBrite = CDbl(arrHSL(2) * 100)

    arrRGB = HSLtoRGB(arrHSL(0), arrHSL(1), intBrite/100)
    newRGB = "#" & HEXtoDEC(arrRGB(0)) & HEXtoDEC(arrRGB(1)) & HEXtoDEC(arrRGB(2))

    ColorBrightness = newRGB
End Function


'::RGB to HSL Function
Function RGBtoHSL(r,g,b)
    r = CDbl(r/255)
    g = CDbl(g/255)
    b = CDbl(b/255)

    max = CDbl(MaxCalc(r & "," & g & "," & b))
    min = CDbl(MinCalc(r & "," & g & "," & b))

    h = CDbl((max + min) / 2)
    s = CDbl((max + min) / 2)
    l = CDbl((max + min) / 2)

    If max = min Then
        h = 0
        s = 0
    Else
        d = max - min
        s = IIf(l > 0.5, d / (2 - max - min), d / (max + min))
        Select Case CStr(max)
            Case CStr(r)
                h = (g - b) / d + (IIf(g < b, 6, 0))
            Case CStr(g)
                h = (b - r) / d + 2
            Case CStr(b)
                h = (r - g) / d + 4
        End Select
        h = h / 6
    End If

    RGBtoHSL = Split(h & "," & s & "," & l, ",")
End Function


'::HSL to RGB Function
Function HSLtoRGB(h,s,l)
    If s = 0 Then
        r = l
        g = l
        b = l
    Else
        q = IIf(l < 0.5, l * (1 + s), l + s - l * s)
        p = 2 * l - q
        r = HUEtoRGB(p, q, h + 1/3)
        g = HUEtoRGB(p, q, h)
        b = HUEtoRGB(p, q, h - 1/3)
    End If

    HSLtoRGB = Split(r * 255 & "," & g * 255 & "," & b * 255, ",")
End Function


'::Hue to RGB Function
Function HUEtoRGB(p,q,t)
    If CDbl(t) < 0 Then t = t + 1
    If CDbl(t) > 1 Then t = t - 1

    If CDbl(t) < (1/6) Then
        HUEtoRGB = p + (q - p) * 6 * t
        Exit Function
    End If

    If CDbl(t) < (1/2) Then
        HUEtoRGB = q
        Exit Function
    End If

    If CDbl(t) < (2/3) Then
        HUEtoRGB = p + (q - p) * (2/3 - t) * 6
        Exit Function
    End If

    HUEtoRGB = p
End Function


'::Hex to Decimal Function
Function HEXtoDEC(d)
    h = Hex(Round(d,0))
    h = Right(String(2,"0") & h,2)
    HEXtoDEC = h
End Function


'::Max Function
Function MaxCalc(valList)
    valList = Split(valList,",")
    b = 0
    For v = 0 To UBound(valList)
        a = valList(v)
        If CDbl(a) > CDbl(b) Then b = a
    Next
    MaxCalc = b
End Function


'::Min Function
Function MinCalc(valList)
    valList = Split(valList,",")
    For v = 0 To UBound(valList)
        a = valList(v)
        If b = "" Then b = a
        If CDbl(a) < CDbl(b) AND b <> "" Then b = a
    Next
    MinCalc = b
End Function


'::IIf Emulation Function
Function IIf(condition,conTrue,conFalse)
    If (condition) Then
        IIf = conTrue
    Else
        IIf = conFalse
    End If
End Function
Shadow The Vaccinated Wizard
  • 62,584
  • 26
  • 129
  • 194
Concept211
  • 886
  • 9
  • 11
1

That way you won't need any conversion of the source color.
check out this fiddle : https://jsfiddle.net/4c47otou/

increase_brightness = function(color,percent){

    var ctx = document.createElement('canvas').getContext('2d');

    ctx.fillStyle = color;
    ctx.fillRect(0,0,1,1);

    var color = ctx.getImageData(0,0,1,1);
    var r = color.data[0] + Math.floor( percent / 100 * 255 );
    var g = color.data[1] + Math.floor( percent / 100 * 255 );
    var b = color.data[2] + Math.floor( percent / 100 * 255 );

    return 'rgb('+r+','+g+','+b+')';
}

Example usage :

increase_brightness('#0000ff',20);
increase_brightness('khaki',20);
increase_brightness('rgb(12, 7, 54)',20);
1

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

Krishnan
  • 71
  • 1
  • 4
1

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)
andersand
  • 432
  • 2
  • 10
1
// color is a hex color like #aaaaaa and percent is a float, 1.00=100%
// increasing a color by 50% means a percent value of 1.5
function brighten(color, percent) {
    var r=parseInt(color.substr(1,2),16);
    var g=parseInt(color.substr(3,2),16);
    var b=parseInt(color.substr(5,2),16);

    return '#'+
       Math.min(255,Math.floor(r*percent)).toString(16)+
       Math.min(255,Math.floor(g*percent)).toString(16)+
       Math.min(255,Math.floor(b*percent)).toString(16);
}

Live sample: http://jsfiddle.net/emM55/

Blindy
  • 55,135
  • 9
  • 81
  • 120
  • 2
    Also, the logic is wrong. "brighten by 50%" actually means "get 50% closer to white". With your logic, anything under 128 doesn't get saturated enough, anything over 128 gets too saturated and anything over 170 becomes 100% saturated – Mark Kahn Jun 22 '11 at 17:46
1

Here is the increaseBrightness function with the RGB->HSL->RGB method. "amount" should be in percent.

HSL<->RGB conversion functions taken from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript

function increaseBrightness( color, amount ) {
    var r = parseInt(color.substr(1, 2), 16);
    var g = parseInt(color.substr(3, 2), 16);
    var b = parseInt(color.substr(5, 2), 16);
    hsl = rgbToHsl( r, g, b );
    hsl.l += hsl.l + (amount / 100);
    if( hsl.l > 1 ) hsl.l = 1;
    rgb = hslToRgb( hsl.h, hsl.s, hsl.l );

    var v = rgb.b | (rgb.g << 8) | (rgb.r << 16);
    return '#' + v.toString(16);
}

function rgbToHsl(r, g, b){
    r /= 255, g /= 255, b /= 255;
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;

    if(max == min){
        h = s = 0; // achromatic
    }else{
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch(max){
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }
    return {'h':h, 's':s, 'l':l};
}

function hslToRgb(h, s, l){
    var r, g, b;

    if(s == 0){
        r = g = b = l; // achromatic
    }else{
        function hue2rgb(p, q, t){
            if(t < 0) t += 1;
            if(t > 1) t -= 1;
            if(t < 1/6) return p + (q - p) * 6 * t;
            if(t < 1/2) return q;
            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        }

        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
    }

    return { 'r':r * 255, 'g':g * 255, 'b':b * 255 };
}
nobody
  • 10,126
  • 4
  • 22
  • 41
  • Thanks for the input! Why not rename `amount` into `percent` if it needs to be in percent? Or like brightnessPercentage ? – Sylhare Feb 14 '18 at 16:59
1

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)
0

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('');
}
0

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)];
}
João Paulo
  • 5,109
  • 3
  • 37
  • 64
0

First get a quick understanding of hex color codes.

Then it should be pretty easy to break down your color value into RGB, make the adjustments and then return the new color code.

Jamie Curtis
  • 924
  • 1
  • 8
  • 24