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
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
**/
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:
Convert RGB to HSL
Set the brightness of HSL
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.
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
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);
// 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/
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 };
}
I found a variation of Sanghyun Lee's reply generates the best result.
Convert RGB to HSL
Set the brightness of HSL
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.
Palette with original approach - gets stuck at certain colors.
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)
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)];
}
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('');
}
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.
Related
I am so blank on this idek where to start.
I am trying to make functions that will manipulate the H S L values once I get them. Mainly the light value, which is why I need it in HSL format.
So once I get it, i would idealy like to create
var h = ;
var s = ;
var l = ;
so i can make the functions to manipulate each one
from the type = 'color' pop up selection, once a colour is selected; it does show its RGB value at the bottom, with arrows changing it to hex or HSL - which I want. So I know the information is there, I just don't know how to get it enter image description here
You can't get the HSL representation of the color from the input element directly. Using .value gives you a hex code, which you can then convert to HSL.
I found the function to convert hex to HSL here.
function getColor() {
let input = document.querySelector('#usr-clr')
let color = HexToHSL(input.value)
console.log('hsl(' + color.h + ', ' + color.s + '%, ' + color.l + '%)')
}
function HexToHSL(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
var r = parseInt(result[1], 16);
var g = parseInt(result[2], 16);
var b = parseInt(result[3], 16);
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;
}
s = s*100;
s = Math.round(s);
l = l*100;
l = Math.round(l);
h = Math.round(360*h);
return {h, s, l};
}
<input type="color" id="usr-clr" onChange="getColor()">
Using javascript/jquery, I want to sort an array of rgba values to the colours of the visible spectrum. By doing this, like shades should be bunched together. Is there a plugin to do this or how would I go about doing it?
Spectrum image: http://www.gamonline.com/catalog/colortheory/images/spectrum.gif
Disclosure: I'm the author of the library recommended below.
If you don't mind using a library, here's a much more concise version of Oriol's detailed response. It uses the sc-color library:
var sorted = colorArray.sort(function(colorA, colorB) {
return sc_color(colorA).hue() - sc_color(colorB).hue();
});
If your array of colors is like this:
var rgbArr = [c1, c2, c3, ...]
where each color ci is an array of three numbers between 0 and 255
ci = [red, green, blue]
then, you can use this function to convert the colors to HSL
function rgbToHsl(c) {
var r = c[0]/255, g = c[1]/255, b = c[2]/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 new Array(h * 360, s * 100, l * 100);
}
and sort them by hue
var sortedRgbArr = rgbArr.map(function(c, i) {
// Convert to HSL and keep track of original indices
return {color: rgbToHsl(c), index: i};
}).sort(function(c1, c2) {
// Sort by hue
return c1.color[0] - c2.color[0];
}).map(function(data) {
// Retrieve original RGB color
return rgbArr[data.index];
});
Here is a runnable example (thanks Ionică Bizău):
function display(container, arr) {
container = document.querySelector(container);
arr.forEach(function(c) {
var el = document.createElement("div");
el.style.backgroundColor = "rgb(" + c.join(", ") + ")";
container.appendChild(el);
})
}
function rgbToHsl(c) {
var r = c[0] / 255,
g = c[1] / 255,
b = c[2] / 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 new Array(h * 360, s * 100, l * 100);
}
var rgbArr = [];
for (var i = 0; i < 100; ++i) {
rgbArr.push([
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256)
]);
}
display("#before", rgbArr);
var sortedRgbArr = rgbArr.map(function(c, i) {
// Convert to HSL and keep track of original indices
return {color: rgbToHsl(c), index: i};
}).sort(function(c1, c2) {
// Sort by hue
return c1.color[0] - c2.color[0];
}).map(function(data) {
// Retrieve original RGB color
return rgbArr[data.index];
});
display("#after", sortedRgbArr);
#before > div,
#after > div {
width: 1%;
height: 20px;
display: inline-block;
}
Random colors: <div id="before"></div>
Same colors, sorted by hue: <div id="after"></div>
sortedRgbArr will contain the rgb colors of rgbArr sorted more or less like the colors of the visible spectrum.
The problem is that the HSL spectrum looks like this:
Your spectrum is weird because it doesn't have all colors, such as pink.
I guess that's because pink doesn't exist in the nature, it's a combination of the colors of the opposite extremes of light's spectrum. But we have it in rgb, so you have to decide where do you want it.
Moreover, it seems that your spectrum goes from lower to higher wavelength, not frequency. But then your spectrum is a reverse of HSL's spectrum.
Replace c1.color[0] - c2.color[0] with c2.color[0] - c1.color[0] if you want it like your spectrum.
Few days ago I started to search for a JavaScript function that generate random colors.
I found a script and made some changes of my own:
function getRandColor(brightness)
{
//6 levels of brightness from 0 to 5, 0 being the darkest
var rgb = [Math.random() * 256, Math.random() * 256, Math.random() * 256];
var mix = [brightness*51, brightness*51, brightness*51]; //51 => 255/5
var mixedrgb = [rgb[0] + mix[0], rgb[1] + mix[1], rgb[2] + mix[2]].map(function(x){ return Math.round(x/2.0)})
var rgb = mixedrgb[2] | (mixedrgb[1] << 8) | (mixedrgb[0] << 16);
return '#' + rgb.toString(16);
}
I would like add a contrast parameter to the function. How can this be done?
Also, If you think there is a faster way to do the calculations, please share. Thanks.
After a lot of search and testing (because I am very bad in this colors things) I made a function to convert hsl to hex color. I am using hsl because it is much easy to work with hsl properties to make the colors you want.
The Function is really bad and ugly, so help me to make it better. For, example the random function returns duplicate values even in 100 calls.
function RandomValue(MinValue,MaxValue)
{
return parseInt(Math.random()*(MaxValue-MinValue+1), 10)+MinValue;
}
function GetRandomColor()
{
var h = RandomValue(1, 360); // color hue between 1 and 360
var s = RandomValue(20, 100); // saturation 0-100%
var l = RandomValue(50, 70); // lightness 0-70%
/* Convert the HSL values to HEX */
return HSLtoRGB(h, s, l);
}
function HSLtoRGB(h, s, v)
{
var r, g, b;
var i;
var f, p, q, t;
h = Math.max(0, Math.min(360, h));
s = Math.max(0, Math.min(100, s));
v = Math.max(0, Math.min(100, v));
s /= 100;
v /= 100;
if(s == 0)
{
r = g = b = v;
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
h /= 60;
i = Math.floor(h);
f = h - i;
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch(i)
{
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default:
r = v;
g = p;
b = q;
}
var rgb = Math.round(b * 255) | (Math.round(g * 255) << 8) | (Math.round(r * 255) << 16);
var m=rgb.toString(16);
switch(m.length)
{
case 5:return '#' + rgb.toString(16) + '0';break;
case 4:return '#' + rgb.toString(16) + '00';break;
default:return '#' + rgb.toString(16);break;
}
}
for (var i = 0; i < 500; i++) {
var div = document.createElement('div');
div.style.backgroundColor = GetRandomColor() ;
document.body.appendChild(div);
}
I found this RGB to HSL script over at http://www.mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript. I can't find any other small decent ones. The issue is that this code doesn't even really work. Would anybody know why? (I don't know a bit of color math, but maybe it's returning the complementary?)
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];
}
Edit: when I run rgbToHsl(126,210,22) it's giving me [ .24, .81, .45 ], which is the HSL for an orange color.
The resulting HSV array has to be interpreted as three fractions. For some programs, if you want to express HSV as integers, you multiply the "H" value by 360 and the "S" and "V" values by 100. The HSV value you quote for your green shade RGB[126, 210, 22] is HSV [87, 81, 45] in integers. You could change the function to return such integers if you want to:
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 [Math.floor(h * 360), Math.floor(s * 100), Math.floor(l * 100)];
}
[edit] that said, it's still giving me something with a brightness ("L" or "V") that's considerably too dark; Gimp says that the HSV value should be [90, 80, 82], or in fractional terms [.20, .80, .82].
[another edit] well one problem could be that HSL and HSV are different schemes ... still looking around.
OK in case anybody wants RGB to HSV (like you'd see in Gimp for example) here's a version of that:
function rgbToHsv(r, g, b) {
var
min = Math.min(r, g, b),
max = Math.max(r, g, b),
delta = max - min,
h, s, v = max;
v = Math.floor(max / 255 * 100);
if ( max != 0 )
s = Math.floor(delta / max * 100);
else {
// black
return [0, 0, 0];
}
if( r == max )
h = ( g - b ) / delta; // between yellow & magenta
else if( g == max )
h = 2 + ( b - r ) / delta; // between cyan & yellow
else
h = 4 + ( r - g ) / delta; // between magenta & cyan
h = Math.floor(h * 60); // degrees
if( h < 0 ) h += 360;
return [h, s, v];
}
edit note that a couple comments suggest that Math.round() might give better answers than Math.floor(), if anybody wants to experiment.
Short but precise
It looks that your code is ok (but it returns hue=0.24 - multiply this by 360 degree to get angle integer value) - however Try this shorter one ( more: hsl2rgb, rgb2hsv, hsv2rgb and sl22sv):
// in: r,g,b in [0,1], out: h in [0,360) and s,l in [0,1]
function rgb2hsl(r,g,b) {
let v=Math.max(r,g,b), c=v-Math.min(r,g,b), f=(1-Math.abs(v+v-c-1));
let h= c && ((v==r) ? (g-b)/c : ((v==g) ? 2+(b-r)/c : 4+(r-g)/c));
return [60*(h<0?h+6:h), f ? c/f : 0, (v+v-c)/2];
}
function rgb2hsl(r,g,b) {
let v=Math.max(r,g,b), c=v-Math.min(r,g,b), f=(1-Math.abs(v+v-c-1));
let h= c && ((v==r) ? (g-b)/c : ((v==g) ? 2+(b-r)/c : 4+(r-g)/c));
return [60*(h<0?h+6:h), f ? c/f : 0, (v+v-c)/2];
}
console.log(`rgb: (0.36,0.3,0.24) --> hsl: (${rgb2hsl(0.36,0.3,0.24)})`);
// ---------------
// UX
// ---------------
rgb= [0,0,0];
hs= [0,0,0];
let $ = x => document.querySelector(x);
let hsl2rgb = (h,s,l, a=s*Math.min(l,1-l), f= (n,k=(n+h/30)%12) => l - a*Math.max(Math.min(k-3,9-k,1),-1)) => [f(0),f(8),f(4)];
function changeRGB(i,e) {
rgb[i]=e.target.value/255;
hs = rgb2hsl(...rgb);
refresh();
}
function changeHS(i,e) {
hs[i]=e.target.value/(i?255:1);
rgb= hsl2rgb(...hs);
refresh();
}
function refresh() {
rr = rgb.map(x=>x*255|0).join(', ')
tr = `RGB: ${rr}`
th = `HSL: ${hs.map((x,i)=>i? (x*100).toFixed(2)+'%':x|0).join(', ')}`
$('.box').style.backgroundColor=`rgb(${rr})`;
$('.infoRGB').innerHTML=`${tr}`;
$('.infoHS').innerHTML =`${th}`;
$('#r').value=rgb[0]*255;
$('#g').value=rgb[1]*255;
$('#b').value=rgb[2]*255;
$('#h').value=hs[0];
$('#s').value=hs[1]*255;
$('#l').value=hs[2]*255;
}
refresh();
.box {
width: 50px;
height: 50px;
margin: 20px;
}
body {
display: flex;
}
<div>
<input id="r" type="range" min="0" max="255" oninput="changeRGB(0,event)">R<br>
<input id="g" type="range" min="0" max="255" oninput="changeRGB(1,event)">G<br>
<input id="b" type="range" min="0" max="255" oninput="changeRGB(2,event)">B<br>
<pre class="infoRGB"></pre>
</div>
<div>
<div class="box hsl"></div>
</div>
<div>
<input id="h" type="range" min="0" max="360" oninput="changeHS(0,event)">H<br>
<input id="s" type="range" min="0" max="255" oninput="changeHS(1,event)">S<br>
<input id="l" type="range" min="0" max="255" oninput="changeHS(2,event)">L<br>
<pre class="infoHS"></pre><br>
</div>
I develop S_HSL wiki formulas (marked by green border) - where MAX=max(r,g,b) and MIN=min(r,g,b) - and in above code I make some improvements and make analysis which shows that results are correct. This allows me to get quite short code at the end
Function below converts RGB color into Hue Saturation Brightness color like Photoshop color picker, results are in the ranges:
Hue 0-360 (degrees)
Saturation: 0-100 (%)
Brightness: 0-100 (%)
I still don't understand why people use the term HSV (Hue Saturation Value) instead of HSB (Hue Saturation Brightness), anyway it's a matter fo terminology, the results are the same
//Converts to color HSB object (code from here http://www.csgnetwork.com/csgcolorsel4.html with some improvements)
function rgb2hsb(r, g, b)
{
r /= 255; g /= 255; b /= 255; // Scale to unity.
var minVal = Math.min(r, g, b),
maxVal = Math.max(r, g, b),
delta = maxVal - minVal,
HSB = {hue:0, sat:0, bri:maxVal},
del_R, del_G, del_B;
if( delta !== 0 )
{
HSB.sat = delta / maxVal;
del_R = (((maxVal - r) / 6) + (delta / 2)) / delta;
del_G = (((maxVal - g) / 6) + (delta / 2)) / delta;
del_B = (((maxVal - b) / 6) + (delta / 2)) / delta;
if (r === maxVal) {HSB.hue = del_B - del_G;}
else if (g === maxVal) {HSB.hue = (1 / 3) + del_R - del_B;}
else if (b === maxVal) {HSB.hue = (2 / 3) + del_G - del_R;}
if (HSB.hue < 0) {HSB.hue += 1;}
if (HSB.hue > 1) {HSB.hue -= 1;}
}
HSB.hue *= 360;
HSB.sat *= 100;
HSB.bri *= 100;
return HSB;
}
Usage example:
var hsb = rgb2hsb(126,210,22);
alert("hue = " + hsb.hue + "saturation = " + hsb.sat + "brightness = " + hsb.bri);
You have to change to the hsl format just like hsl(155,100%,30%), that can be use in HTML.
//this change hsl to : "hsl(155,100%,30%)"
function hsl2str({h,s,l,a=1}) {
return "hsl("+h+","+s*100+"%,"+l*100+"%,"+a+")";
}
//for html, the h is 0-360, so you need this function
function rgb2hsl({r,g,b,a=1}) {
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;
}
}
return {h: h*60,s,l,a};
}
Does anybody know, off the top of your heads, a Javascript solution for calculating the complementary colour of a hex value?
There are a number of colour picking suites and palette generators on the web but I haven't seen any that actually calculate the colour dynamically using JS.
A detailed hint or a snippet would be very much appreciated.
Parsed through http://design.geckotribe.com/colorwheel/
// Complement
temprgb={ r: 0, g: 0xff, b: 0xff }; // Cyan
temphsv=RGB2HSV(temprgb);
temphsv.hue=HueShift(temphsv.hue,180.0);
temprgb=HSV2RGB(temphsv);
console.log(temprgb); // Complement is red (0xff, 0, 0)
function RGB2HSV(rgb) {
hsv = new Object();
max=max3(rgb.r,rgb.g,rgb.b);
dif=max-min3(rgb.r,rgb.g,rgb.b);
hsv.saturation=(max==0.0)?0:(100*dif/max);
if (hsv.saturation==0) hsv.hue=0;
else if (rgb.r==max) hsv.hue=60.0*(rgb.g-rgb.b)/dif;
else if (rgb.g==max) hsv.hue=120.0+60.0*(rgb.b-rgb.r)/dif;
else if (rgb.b==max) hsv.hue=240.0+60.0*(rgb.r-rgb.g)/dif;
if (hsv.hue<0.0) hsv.hue+=360.0;
hsv.value=Math.round(max*100/255);
hsv.hue=Math.round(hsv.hue);
hsv.saturation=Math.round(hsv.saturation);
return hsv;
}
// RGB2HSV and HSV2RGB are based on Color Match Remix [http://color.twysted.net/]
// which is based on or copied from ColorMatch 5K [http://colormatch.dk/]
function HSV2RGB(hsv) {
var rgb=new Object();
if (hsv.saturation==0) {
rgb.r=rgb.g=rgb.b=Math.round(hsv.value*2.55);
} else {
hsv.hue/=60;
hsv.saturation/=100;
hsv.value/=100;
i=Math.floor(hsv.hue);
f=hsv.hue-i;
p=hsv.value*(1-hsv.saturation);
q=hsv.value*(1-hsv.saturation*f);
t=hsv.value*(1-hsv.saturation*(1-f));
switch(i) {
case 0: rgb.r=hsv.value; rgb.g=t; rgb.b=p; break;
case 1: rgb.r=q; rgb.g=hsv.value; rgb.b=p; break;
case 2: rgb.r=p; rgb.g=hsv.value; rgb.b=t; break;
case 3: rgb.r=p; rgb.g=q; rgb.b=hsv.value; break;
case 4: rgb.r=t; rgb.g=p; rgb.b=hsv.value; break;
default: rgb.r=hsv.value; rgb.g=p; rgb.b=q;
}
rgb.r=Math.round(rgb.r*255);
rgb.g=Math.round(rgb.g*255);
rgb.b=Math.round(rgb.b*255);
}
return rgb;
}
//Adding HueShift via Jacob (see comments)
function HueShift(h,s) {
h+=s; while (h>=360.0) h-=360.0; while (h<0.0) h+=360.0; return h;
}
//min max via Hairgami_Master (see comments)
function min3(a,b,c) {
return (a<b)?((a<c)?a:c):((b<c)?b:c);
}
function max3(a,b,c) {
return (a>b)?((a>c)?a:c):((b>c)?b:c);
}
I find that taking the bit-wise complement works well, and quickly.
var color = 0x320ae3;
var complement = 0xffffff ^ color;
I'm not sure if it's a perfect complement in the sense of "mixes together to form a 70% grey", however a 70% grey is "pure white" in terms of color timing in film. It occurred to me that XORing the RGB hex out of pure white might be a good first approximation. You could also try a darker grey to see how that works for you.
Again, this is a fast approximation and I make no guarantees that it'll be perfectly accurate.
See https://github.com/alfl/textful/blob/master/app.js#L38 for my implementation.
None of the other functions here worked out the box, so I made this one.
It takes a hex value, converts it to HSL, shifts the hue 180 degrees and converts back to Hex
/* hexToComplimentary : Converts hex value to HSL, shifts
* hue by 180 degrees and then converts hex, giving complimentary color
* as a hex value
* #param [String] hex : hex value
* #return [String] : complimentary color as hex value
*/
function hexToComplimentary(hex){
// Convert hex to rgb
// Credit to Denis http://stackoverflow.com/a/36253499/4939630
var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')';
// Get array of RGB values
rgb = rgb.replace(/[^\d,]/g, '').split(',');
var r = rgb[0], g = rgb[1], b = rgb[2];
// Convert RGB to HSL
// Adapted from answer by 0x000f http://stackoverflow.com/a/34946092/4939630
r /= 255.0;
g /= 255.0;
b /= 255.0;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, l = (max + min) / 2.0;
if(max == min) {
h = s = 0; //achromatic
} else {
var d = max - min;
s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));
if(max == r && g >= b) {
h = 1.0472 * (g - b) / d ;
} else if(max == r && g < b) {
h = 1.0472 * (g - b) / d + 6.2832;
} else if(max == g) {
h = 1.0472 * (b - r) / d + 2.0944;
} else if(max == b) {
h = 1.0472 * (r - g) / d + 4.1888;
}
}
h = h / 6.2832 * 360.0 + 0;
// Shift hue to opposite side of wheel and convert to [0-1] value
h+= 180;
if (h > 360) { h -= 360; }
h /= 360;
// Convert h s and l values into r g and b values
// Adapted from answer by Mohsen http://stackoverflow.com/a/9493060/4939630
if(s === 0){
r = g = b = l; // achromatic
} else {
var hue2rgb = 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);
}
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
// Convert r b and g values to hex
rgb = b | (g << 8) | (r << 16);
return "#" + (0x1000000 | rgb).toString(16).substring(1);
}
Rather than reinventing the wheel, I found a library to work with colors.
Tiny Color
This is how you would implement some of the other answers using it.
color1 = tinycolor2('#f00').spin(180).toHexString(); // Hue Shift
color2 = tinycolor2("#f00").complement().toHexString(); // bitwise
Hex and RGB complementry
This is most correct and efficient way to get complementary hex color value
function complementryHexColor(hex){
let r = hex.length == 4 ? parseInt(hex[1] + hex[1], 16) : parseInt(hex.slice(1, 3), 16);
let g = hex.length == 4 ? parseInt(hex[2] + hex[2], 16) : parseInt(hex.slice(3, 5), 16);
let b = hex.length == 4 ? parseInt(hex[3] + hex[3], 16) : parseInt(hex.slice(5), 16);
[r, g, b] = complementryRGBColor(r, g, b);
return '#' + (r < 16 ? '0' + r.toString(16) : r.toString(16)) + (g < 16 ? '0' + g.toString(16) : g.toString(16)) + (b < 16 ? '0' + b.toString(16) : b.toString(16));
}
function complementryRGBColor(r, g, b) {
if (Math.max(r, g, b) == Math.min(r, g, b)) {
return [255 - r, 255 - g, 255 - b];
} else {
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;
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 = Math.round((h*60) + 180) % 360;
h /= 360;
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 [Math.round(r*255), Math.round(g*255), Math.round(b*255)];
}
}
RGB Complimentary
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function rgbComplimentary(r,g,b){
var hex = "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')';
// Get array of RGB values
rgb = rgb.replace(/[^\d,]/g, '').split(',');
var r = rgb[0]/255.0, g = rgb[1]/255.0, b = rgb[2]/255.0;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, l = (max + min) / 2.0;
if(max == min) {
h = s = 0; //achromatic
} else {
var d = max - min;
s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));
if(max == r && g >= b) {
h = 1.0472 * (g - b) / d ;
} else if(max == r && g < b) {
h = 1.0472 * (g - b) / d + 6.2832;
} else if(max == g) {
h = 1.0472 * (b - r) / d + 2.0944;
} else if(max == b) {
h = 1.0472 * (r - g) / d + 4.1888;
}
}
h = h / 6.2832 * 360.0 + 0;
// Shift hue to opposite side of wheel and convert to [0-1] value
h+= 180;
if (h > 360) { h -= 360; }
h /= 360;
// Convert h s and l values into r g and b values
// Adapted from answer by Mohsen http://stackoverflow.com/a/9493060/4939630
if(s === 0){
r = g = b = l; // achromatic
} else {
var hue2rgb = 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);
}
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
// Convert r b and g values to hex
rgb = b | (g << 8) | (r << 16);
return hexToRgb("#" + (0x1000000 | rgb).toString(16).substring(1));
}
console.log(rgbComplimentary(242, 211, 215));
HEX Complimentary
function hexComplimentary(hex){
var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')';
// Get array of RGB values
rgb = rgb.replace(/[^\d,]/g, '').split(',');
var r = rgb[0]/255.0, g = rgb[1]/255.0, b = rgb[2]/255.0;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, l = (max + min) / 2.0;
if(max == min) {
h = s = 0; //achromatic
} else {
var d = max - min;
s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));
if(max == r && g >= b) {
h = 1.0472 * (g - b) / d ;
} else if(max == r && g < b) {
h = 1.0472 * (g - b) / d + 6.2832;
} else if(max == g) {
h = 1.0472 * (b - r) / d + 2.0944;
} else if(max == b) {
h = 1.0472 * (r - g) / d + 4.1888;
}
}
h = h / 6.2832 * 360.0 + 0;
// Shift hue to opposite side of wheel and convert to [0-1] value
h+= 180;
if (h > 360) { h -= 360; }
h /= 360;
if(s === 0){
r = g = b = l; // achromatic
} else {
var hue2rgb = 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);
}
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
// Convert r b and g values to hex
rgb = b | (g << 8) | (r << 16);
return "#" + (0x1000000 | rgb).toString(16).substring(1);
}
console.log(hexComplimentary("#ff5a5a"));
Source: Updated https://stackoverflow.com/a/37657940/6569224 answer