How to create table with a gradient rows using JS? [duplicate] - javascript

Here is a function I was working on to programmatically lighten or darken a hex color by a specific amount. Just pass in a string like "3F6D2A" for the color (col) and a base10 integer (amt) for the amount to lighten or darken. To darken, pass in a negative number (i.e. -20).
The reason for me to do this was because of all the solutions I found, thus far, they seemed to over-complicate the issue. And I had a feeling it could be done with just a couple lines of code. Please let me know if you find any problems, or have any adjustments to make that would speed it up.
function LightenDarkenColor(col, amt) {
col = parseInt(col, 16);
return (((col & 0x0000FF) + amt) | ((((col >> 8) & 0x00FF) + amt) << 8) | (((col >> 16) + amt) << 16)).toString(16);
}
// TEST
console.log( LightenDarkenColor("3F6D2A",40) );
For Development use here is an easier to read version:
function LightenDarkenColor(col, amt) {
var num = parseInt(col, 16);
var r = (num >> 16) + amt;
var b = ((num >> 8) & 0x00FF) + amt;
var g = (num & 0x0000FF) + amt;
var newColor = g | (b << 8) | (r << 16);
return newColor.toString(16);
}
// TEST
console.log(LightenDarkenColor("3F6D2A", -40));
And finally a version to handle colors that may (or may not) have the "#" in the beginning. Plus adjusting for improper color values:
function LightenDarkenColor(col,amt) {
var usePound = false;
if ( col[0] == "#" ) {
col = col.slice(1);
usePound = true;
}
var num = parseInt(col,16);
var r = (num >> 16) + amt;
if ( r > 255 ) r = 255;
else if (r < 0) r = 0;
var b = ((num >> 8) & 0x00FF) + amt;
if ( b > 255 ) b = 255;
else if (b < 0) b = 0;
var g = (num & 0x0000FF) + amt;
if ( g > 255 ) g = 255;
else if ( g < 0 ) g = 0;
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
}
OK, so now it's not just a couple of lines, but it seems far simpler and if you're not using the "#" and don't need to check for colors out of range, it is only a couple of lines.
If not using the "#", you can just add it in code like:
var myColor = "3F6D2A";
myColor = LightenDarkenColor(myColor,10);
thePlaceTheColorIsUsed = ("#" + myColor);
I guess my main question is, am I correct here? Does this not encompass most (normal) situations? And if so, what is the fastest and smallest way to do this? I want to use in animations and in a small environment, so speed is the first most important factor here, size second, accuracy third, readability? huh? not on the list of requirements (sorry, I know half of you are tearing out your eyes right now!).

Well, this answer has become its own beast. Many new versions, it was getting stupid long. Many thanks to all of the great many contributors to this answer. But, in order to keep it simple for the masses. I archived all the versions/history of this answer's evolution to my github. And started it over clean on StackOverflow here with the newest version. A special thanks goes out to Mike 'Pomax' Kamermans for this version. He gave me the new math.
This function (pSBC) will take a HEX or RGB web color. pSBC can shade it darker or lighter, or blend it with a second color, and can also pass it right thru but convert from Hex to RGB (Hex2RGB) or RGB to Hex (RGB2Hex). All without you even knowing what color format you are using.
This runs really fast, probably the fastest, especially considering its many features. It was a long time in the making. See the whole story on my github. If you want the absolutely smallest and fastest possible way to shade or blend, see the Micro Functions below and use one of the 2-liner speed demons. They are great for intense animations, but this version here is fast enough for most animations.
This function uses Log Blending or Linear Blending. However, it does NOT convert to HSL to properly lighten or darken a color. Therefore, results from this function will differ from those much larger and much slower functions that use HSL.
jsFiddle with pSBC
github > pSBC Wiki
Features:
Auto-detects and accepts standard Hex colors in the form of strings. For example: "#AA6622" or "#bb551144".
Auto-detects and accepts standard RGB colors in the form of strings. For example: "rgb(123,45,76)" or "rgba(45,15,74,0.45)".
Shades colors to white or black by percentage.
Blends colors together by percentage.
Does Hex2RGB and RGB2Hex conversion at the same time, or solo.
Accepts 3 digit (or 4 digit w/ alpha) HEX color codes, in the form #RGB (or #RGBA). It will expand them. For Example: "#C41" becomes "#CC4411".
Accepts and (Linear) blends alpha channels. If either the c0 (from) color or the c1 (to) color has an alpha channel, then the returned color will have an alpha channel. If both colors have an alpha channel, then the returned color will be a linear blend of the two alpha channels using the percentage given (just as if it were a normal color channel). If only one of the two colors has an alpha channel, this alpha will just be passed thru to the returned color. This allows one to blend/shade a transparent color while maintaining the transparency level. Or, if the transparency levels should blend as well, make sure both colors have alphas. When shading, it will pass the alpha channel straight thru. If you want basic shading that also shades the alpha channel, then use rgb(0,0,0,1) or rgb(255,255,255,1) as your c1 (to) color (or their hex equivalents). For RGB colors, the returned color's alpha channel will be rounded to 3 decimal places.
RGB2Hex and Hex2RGB conversions are implicit when using blending. Regardless of the c0 (from) color; the returned color will always be in the color format of the c1 (to) color, if one exists. If there is no c1 (to) color, then pass 'c' in as the c1 color and it will shade and convert whatever the c0 color is. If conversion only is desired, then pass 0 in as the percentage (p) as well. If the c1 color is omitted or a non-string is passed in, it will not convert.
A secondary function is added to the global as well. pSBCr can be passed a Hex or RGB color and it returns an object containing this color information. Its in the form: {r: XXX, g: XXX, b: XXX, a: X.XXX}. Where .r, .g, and .b have range 0 to 255. And when there is no alpha: .a is -1. Otherwise: .a has range 0.000 to 1.000.
For RGB output, it outputs rgba() over rgb() when a color with an alpha channel was passed into c0 (from) and/or c1 (to).
Minor Error Checking has been added. It's not perfect. It can still crash or create jibberish. But it will catch some stuff. Basically, if the structure is wrong in some ways or if the percentage is not a number or out of scope, it will return null. An example: pSBC(0.5,"salt") == null, where as it thinks #salt is a valid color. Delete the four lines which end with return null; to remove this feature and make it faster and smaller.
Uses Log Blending. Pass true in for l (the 4th parameter) to use Linear Blending.
Code:
// Version 4.0
const pSBC=(p,c0,c1,l)=>{
let r,g,b,P,f,t,h,i=parseInt,m=Math.round,a=typeof(c1)=="string";
if(typeof(p)!="number"||p<-1||p>1||typeof(c0)!="string"||(c0[0]!='r'&&c0[0]!='#')||(c1&&!a))return null;
if(!this.pSBCr)this.pSBCr=(d)=>{
let n=d.length,x={};
if(n>9){
[r,g,b,a]=d=d.split(","),n=d.length;
if(n<3||n>4)return null;
x.r=i(r[3]=="a"?r.slice(5):r.slice(4)),x.g=i(g),x.b=i(b),x.a=a?parseFloat(a):-1
}else{
if(n==8||n==6||n<4)return null;
if(n<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(n>4?d[4]+d[4]:"");
d=i(d.slice(1),16);
if(n==9||n==5)x.r=d>>24&255,x.g=d>>16&255,x.b=d>>8&255,x.a=m((d&255)/0.255)/1000;
else x.r=d>>16,x.g=d>>8&255,x.b=d&255,x.a=-1
}return x};
h=c0.length>9,h=a?c1.length>9?true:c1=="c"?!h:false:h,f=this.pSBCr(c0),P=p<0,t=c1&&c1!="c"?this.pSBCr(c1):P?{r:0,g:0,b:0,a:-1}:{r:255,g:255,b:255,a:-1},p=P?p*-1:p,P=1-p;
if(!f||!t)return null;
if(l)r=m(P*f.r+p*t.r),g=m(P*f.g+p*t.g),b=m(P*f.b+p*t.b);
else r=m((P*f.r**2+p*t.r**2)**0.5),g=m((P*f.g**2+p*t.g**2)**0.5),b=m((P*f.b**2+p*t.b**2)**0.5);
a=f.a,t=t.a,f=a>=0||t>=0,a=f?a<0?t:t<0?a:a*P+t*p:0;
if(h)return"rgb"+(f?"a(":"(")+r+","+g+","+b+(f?","+m(a*1000)/1000:"")+")";
else return"#"+(4294967296+r*16777216+g*65536+b*256+(f?m(a*255):0)).toString(16).slice(1,f?undefined:-2)
}
Usage:
// Setup:
let color1 = "rgb(20,60,200)";
let color2 = "rgba(20,60,200,0.67423)";
let color3 = "#67DAF0";
let color4 = "#5567DAF0";
let color5 = "#F3A";
let color6 = "#F3A9";
let color7 = "rgb(200,60,20)";
let color8 = "rgba(200,60,20,0.98631)";
// Tests:
/*** Log Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1 ); // rgb(20,60,200) + [42% Lighter] => rgb(166,171,225)
pSBC ( -0.4, color5 ); // #F3A + [40% Darker] => #c62884
pSBC ( 0.42, color8 ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(225,171,166,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c" ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #a6abe1ac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c" ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8 ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(142,60,142,0.83)
pSBC ( 0.7, color2, color7 ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(168,60,111,0.67423)
pSBC ( 0.25, color3, color7 ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(134,191,208)
pSBC ( 0.75, color7, color3 ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #86bfd0
/*** Linear Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1, false, true ); // rgb(20,60,200) + [42% Lighter] => rgb(119,142,223)
pSBC ( -0.4, color5, false, true ); // #F3A + [40% Darker] => #991f66
pSBC ( 0.42, color8, false, true ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(223,142,119,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c", true ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #778edfac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c", true ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8, true ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(110,60,110,0.83)
pSBC ( 0.7, color2, color7, true ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(146,60,74,0.67423)
pSBC ( 0.25, color3, color7, true ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(127,179,185)
pSBC ( 0.75, color7, color3, true ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #7fb3b9
/*** Other Stuff ***/
// Error Checking
pSBC ( 0.42, "#FFBAA" ); // #FFBAA + [42% Lighter] => null  (Invalid Input Color)
pSBC ( 42, color1, color5 ); // rgb(20,60,200) + #F3A + [4200% Blend] => null  (Invalid Percentage Range)
pSBC ( 0.42, {} ); // [object Object] + [42% Lighter] => null  (Strings Only for Color)
pSBC ( "42", color1 ); // rgb(20,60,200) + ["42"] => null  (Numbers Only for Percentage)
pSBC ( 0.42, "salt" ); // salt + [42% Lighter] => null  (A Little Salt is No Good...)
// Error Check Fails (Some Errors are not Caught)
pSBC ( 0.42, "#salt" ); // #salt + [42% Lighter] => #a5a5a500  (...and a Pound of Salt is Jibberish)
// Ripping
pSBCr ( color4 ); // #5567DAF0 + [Rip] => [object Object] => {'r':85,'g':103,'b':218,'a':0.941}
The picture below will help show the difference in the two blending methods:
Micro Functions
If you really want speed and size, you will have to use RGB not HEX. RGB is more straightforward and simple, HEX writes too slow and comes in too many flavors for a simple two-liner (IE. it could be a 3, 4, 6, or 8 digit HEX code). You will also need to sacrifice some features, no error checking, no HEX2RGB nor RGB2HEX. As well, you will need to choose a specific function (based on its function name below) for the color blending math, and if you want shading or blending. These functions do support alpha channels. And when both input colors have alphas it will Linear Blend them. If only one of the two colors has an alpha, it will pass it straight thru to the resulting color. Below are two liner functions that are incredibly fast and small:
const RGB_Linear_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+i(e[3]=="a"?e.slice(5):e.slice(4))*p)+","+r(i(b)*P+i(f)*p)+","+r(i(c)*P+i(g)*p)+j;
}
const RGB_Linear_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:255*p,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+t)+","+r(i(b)*P+t)+","+r(i(c)*P+t)+(d?","+d:")");
}
const RGB_Log_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+p*i(e[3]=="a"?e.slice(5):e.slice(4))**2)**0.5)+","+r((P*i(b)**2+p*i(f)**2)**0.5)+","+r((P*i(c)**2+p*i(g)**2)**0.5)+j;
}
const RGB_Log_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:p*255**2,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+t)**0.5)+","+r((P*i(b)**2+t)**0.5)+","+r((P*i(c)**2+t)**0.5)+(d?","+d:")");
}
Want more info? Read the full writeup on github.
PT
(P.s. If anyone has the math for another blending method, please share.)

I made a solution that works very nice for me:
function shadeColor(color, percent) {
var R = parseInt(color.substring(1,3),16);
var G = parseInt(color.substring(3,5),16);
var B = parseInt(color.substring(5,7),16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R<255)?R:255;
G = (G<255)?G:255;
B = (B<255)?B:255;
R = Math.round(R)
G = Math.round(G)
B = Math.round(B)
var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));
return "#"+RR+GG+BB;
}
Example Lighten:
shadeColor("#63C6FF",40);
Example Darken:
shadeColor("#63C6FF",-40);

Here is a super simple one liner based on Eric's answer
function adjust(color, amount) {
return '#' + color.replace(/^#/, '').replace(/../g, color => ('0'+Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
}
Examples:
adjust('#ffffff', -20) => "#ebebeb"
adjust('000000', 20) => "#141414"

I am adding my 2 cents here, a satisfyingly small combination of different answers:
const colorShade = (col, amt) => {
col = col.replace(/^#/, '')
if (col.length === 3) col = col[0] + col[0] + col[1] + col[1] + col[2] + col[2]
let [r, g, b] = col.match(/.{2}/g);
([r, g, b] = [parseInt(r, 16) + amt, parseInt(g, 16) + amt, parseInt(b, 16) + amt])
r = Math.max(Math.min(255, r), 0).toString(16)
g = Math.max(Math.min(255, g), 0).toString(16)
b = Math.max(Math.min(255, b), 0).toString(16)
const rr = (r.length < 2 ? '0' : '') + r
const gg = (g.length < 2 ? '0' : '') + g
const bb = (b.length < 2 ? '0' : '') + b
return `#${rr}${gg}${bb}`
}
accepts a color starting with # or not, with 6 characters or 3 characters.
example of use: colorShade('#54b946', -40)
Here is the output of 4 colors with 3 shades lighter and 3 shades darker for each of them (amount is a multiple of 40 here).

This is what I used based on your function. I prefer to use steps over percentage because it's more intuitive for me.
For example, 20% of a 200 blue value is much different than 20% of a 40 blue value.
Anyways, here's my modification, thanks for your original function.
function adjustBrightness(col, amt) {
var usePound = false;
if (col[0] == "#") {
col = col.slice(1);
usePound = true;
}
var R = parseInt(col.substring(0,2),16);
var G = parseInt(col.substring(2,4),16);
var B = parseInt(col.substring(4,6),16);
// to make the colour less bright than the input
// change the following three "+" symbols to "-"
R = R + amt;
G = G + amt;
B = B + amt;
if (R > 255) R = 255;
else if (R < 0) R = 0;
if (G > 255) G = 255;
else if (G < 0) G = 0;
if (B > 255) B = 255;
else if (B < 0) B = 0;
var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));
return (usePound?"#":"") + RR + GG + BB;
}

Based on the, David Sherret and Pablo, answer above converted the solution to safer version for Typescript
/**
* #param color Hex value format: #ffffff or ffffff
* #param decimal lighten or darken decimal value, example 0.5 to lighten by 50% or 1.5 to darken by 50%.
*/
static shadeColor(color: string, decimal: number): string {
const base = color.startsWith('#') ? 1 : 0;
let r = parseInt(color.substring(base, 3), 16);
let g = parseInt(color.substring(base + 2, 5), 16);
let b = parseInt(color.substring(base + 4, 7), 16);
r = Math.round(r / decimal);
g = Math.round(g / decimal);
b = Math.round(b / decimal);
r = (r < 255)? r : 255;
g = (g < 255)? g : 255;
b = (b < 255)? b : 255;
const rr = ((r.toString(16).length === 1)? `0${r.toString(16)}` : r.toString(16));
const gg = ((g.toString(16).length === 1)? `0${g.toString(16)}` : g.toString(16));
const bb = ((b.toString(16).length === 1)? `0${b.toString(16)}` : b.toString(16));
return `#${rr}${gg}${bb}`;
}

I tried your function and there was a little bug: If some final 'r' value is 1 digit only, the result comes up like: 'a0a0a' when the right value is '0a0a0a', for example.
I just quick-fixed it by adding this instead of your return:
var rStr = (r.toString(16).length < 2)?'0'+r.toString(16):r.toString(16);
var gStr = (g.toString(16).length < 2)?'0'+g.toString(16):g.toString(16);
var bStr = (b.toString(16).length < 2)?'0'+b.toString(16):b.toString(16);
return (usePound?"#":"") + rStr + gStr + bStr;
Maybe it's not so nice but it do the work. Great function, BTW. Just what I needed. :)

have you thought about an rgb > hsl conversion? then just move the Luminosity up and down? thats the way I would go.
A quick look for some algorithms got me the following sites.
PHP:
http://serennu.com/colour/rgbtohsl.php
Javascript:
http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
EDIT the above link is no longer valid. You can view git hub for the page source or the gist
Alternatively another StackOverflow question might be a good place to look.
Even though this is not the right choice for the OP the following is an approximation of the code I was originally suggesting. (Assuming you have rgb/hsl conversion functions)
var SHADE_SHIFT_AMOUNT = 0.1;
function lightenShade(colorValue)
{
if(colorValue && colorValue.length >= 6)
{
var redValue = parseInt(colorValue.slice(-6,-4), 16);
var greenValue = parseInt(colorValue.slice(-4,-2), 16);
var blueValue = parseInt(colorValue.slice(-2), 16);
var hsl = rgbToHsl(redValue, greenValue, blueValue);
hsl[2]= Math.min(hsl[2] + SHADE_SHIFT_AMOUNT, 1);
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
}
return null;
}
function darkenShade(colorValue)
{
if(colorValue && colorValue.length >= 6)
{
var redValue = parseInt(colorValue.slice(-6,-4), 16);
var greenValue = parseInt(colorValue.slice(-4,-2), 16);
var blueValue = parseInt(colorValue.slice(-2), 16);
var hsl = rgbToHsl(redValue, greenValue, blueValue);
hsl[2]= Math.max(hsl[2] - SHADE_SHIFT_AMOUNT, 0);
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
}
return null;
}
This assumes:
You have functions hslToRgb and rgbToHsl.
The parameter colorValue is a string in the form #RRGGBB
Although if we are discussing css there is a syntax for specifying hsl/hsla for IE9/Chrome/Firefox.

I wanted to change a color to a specific brightness level - no matter what brightness the color was before - here's a simple JS function that seems to work well, although I'm sure it could be shorter
function setLightPercentage(col: any, p: number) {
const R = parseInt(col.substring(1, 3), 16);
const G = parseInt(col.substring(3, 5), 16);
const B = parseInt(col.substring(5, 7), 16);
const curr_total_dark = (255 * 3) - (R + G + B);
// calculate how much of the current darkness comes from the different channels
const RR = ((255 - R) / curr_total_dark);
const GR = ((255 - G) / curr_total_dark);
const BR = ((255 - B) / curr_total_dark);
// calculate how much darkness there should be in the new color
const new_total_dark = ((255 - 255 * (p / 100)) * 3);
// make the new channels contain the same % of available dark as the old ones did
const NR = 255 - Math.round(RR * new_total_dark);
const NG = 255 - Math.round(GR * new_total_dark);
const NB = 255 - Math.round(BR * new_total_dark);
const RO = ((NR.toString(16).length === 1) ? "0" + NR.toString(16) : NR.toString(16));
const GO = ((NG.toString(16).length === 1) ? "0" + NG.toString(16) : NG.toString(16));
const BO = ((NB.toString(16).length === 1) ? "0" + NB.toString(16) : NB.toString(16));
return "#" + RO + GO + BO;}

Your approach is ok :) I simplify your shortest version a little (for saturation control look here)
(col,amt)=> (+('0x'+col)+amt*0x010101).toString(16).padStart(6,0)
// Similar to OP shortest version, we not have here # and colors range checking
var LightenDarkenColor =
(col,amt) => (+('0x'+col)+amt*0x010101).toString(16).padStart(6,0);
// ------
// TEST
// ------
function update() {
let c= col.value.padEnd(6,'0').slice(0,6);
let color = '#'+LightenDarkenColor(c, +amt.value);
oldColor.innerHTML = 'Old: #'+c;
oldColor.style = `background: #${c}`;
newColor.innerHTML = 'New: '+color
newColor.style = `background: ${color}`;
}
update();
.box{ width: 100px; height: 100px; margin: 10px; display: inline-block}
<input id="col" value="3F6D2A" oninput="update()">
<input id="amt" value="30" oninput="update()"><br>
<div id="oldColor" class="box"></div>
<div id="newColor" class="box"></div>
And version with # and color ranges checking
// # and colors range checking
var LightenDarkenColor =
(col,amt) => '#'+col.slice(1).match(/../g)
.map(x=>(x=+`0x${x}`+amt,x<0?0:(x>255?255:x))
.toString(16).padStart(2,0)).join``;
// ------
// TEST
// ------
function update() {
let c= col.value.padEnd(6,'0').slice(0,7);
let color = LightenDarkenColor(c, +amt.value);
oldColor.innerHTML = 'Old: '+c;
oldColor.style = `background: ${c}`;
newColor.innerHTML = 'New: '+color
newColor.style = `background: ${color}`;
}
update();
.box{ width: 100px; height: 100px; margin: 10px; display: inline-block}
<input id="col" value="#3F6D2A" oninput="update()">
<input id="amt" value="40" oninput="update()"><br>
<div id="oldColor" class="box"></div>
<div id="newColor" class="box"></div>

C# Version...
note that I am getting color strings in this format #FF12AE34, and need to cut out the #FF.
private string GetSmartShadeColorByBase(string s, float percent)
{
if (string.IsNullOrEmpty(s))
return "";
var r = s.Substring(3, 2);
int rInt = int.Parse(r, NumberStyles.HexNumber);
var g = s.Substring(5, 2);
int gInt = int.Parse(g, NumberStyles.HexNumber);
var b = s.Substring(7, 2);
int bInt = int.Parse(b, NumberStyles.HexNumber);
var t = percent < 0 ? 0 : 255;
var p = percent < 0 ? percent*-1 : percent;
int newR = Convert.ToInt32(Math.Round((t - rInt) * p) + rInt);
var newG = Convert.ToInt32(Math.Round((t - gInt) * p) + gInt);
var newB = Convert.ToInt32(Math.Round((t - bInt) * p) + bInt);
return String.Format("#{0:X2}{1:X2}{2:X2}", newR, newG, newB);
}

I just used the hex number preceded by '#'.
var x = 0xf0f0f0;
x=x+0xf00; //set this value as you wish programatically
document.getElementById("heading").style = 'background-color: #'+x.toString(16);
higher the number ..lighter the color

I needed it in C#, it may help .net developers
public static string LightenDarkenColor(string color, int amount)
{
int colorHex = int.Parse(color, System.Globalization.NumberStyles.HexNumber);
string output = (((colorHex & 0x0000FF) + amount) | ((((colorHex >> 0x8) & 0x00FF) + amount) << 0x8) | (((colorHex >> 0xF) + amount) << 0xF)).ToString("x6");
return output;
}

My version written in typescript:
function changeColorLightness(color: number, lightness: number): number {
return (Math.max(0, Math.min(((color & 0xFF0000) / 0x10000) + lightness, 0xFF)) * 0x10000) +
(Math.max(0, Math.min(((color & 0x00FF00) / 0x100) + lightness, 0xFF)) * 0x100) +
(Math.max(0, Math.min(((color & 0x0000FF)) + lightness, 0xFF)));
}
explanation:
export function changeColorLightness(color: number, lightness: number): number {
const r = (color & 0xFF0000) / 0x10**4;
const g = (color & 0x00FF00) / 0x10**2;
const b = (color & 0x0000FF);
const changedR = Math.max(0, Math.min(r + lightness, 0xFF));
const changedG = Math.max(0, Math.min(g + lightness, 0xFF));
const changedB = Math.max(0, Math.min(b + lightness, 0xFF));
return (changedR * 0x10**4) + (changedG * 0x10**2) + changedB;
}
usage:
changeColorLightness(0x00FF00, 0x50);
changeColorLightness(parseInt("#00FF00".replace('#',''), 16), 0x50);
changeColorLightness(0x00FF00, 127.5);

The following method will allow you to lighten or darken the exposure value of a Hexadecimal (Hex) color string:
private static string GetHexFromRGB(byte r, byte g, byte b, double exposure)
{
exposure = Math.Max(Math.Min(exposure, 1.0), -1.0);
if (exposure >= 0)
{
return "#"
+ ((byte)(r + ((byte.MaxValue - r) * exposure))).ToString("X2")
+ ((byte)(g + ((byte.MaxValue - g) * exposure))).ToString("X2")
+ ((byte)(b + ((byte.MaxValue - b) * exposure))).ToString("X2");
}
else
{
return "#"
+ ((byte)(r + (r * exposure))).ToString("X2")
+ ((byte)(g + (g * exposure))).ToString("X2")
+ ((byte)(b + (b * exposure))).ToString("X2");
}
}
For the last parameter value in GetHexFromRGB(), Pass in a double value somewhere between -1 and 1 (-1 is black, 0 is unchanged, 1 is white):
// split color (#e04006) into three strings
var r = Convert.ToByte("e0", 16);
var g = Convert.ToByte("40", 16);
var b = Convert.ToByte("06", 16);
GetHexFromRGB(r, g, b, 0.25); // Lighten by 25%;

There is lack of support for colors starting from 00 ie "#000623" but here is the fix
function lightenDarkenColor(colorCode, amount) {
let usePound = false;
if (colorCode[0] == "#") {
colorCode = colorCode.slice(1);
usePound = true;
}
const num = parseInt(colorCode, 16);
let r = (num >> 16) + amount;
if (r > 255) {
r = 255;
} else if (r < 0) {
r = 0;
}
let b = ((num >> 8) & 0x00FF) + amount;
if (b > 255) {
b = 255;
} else if (b < 0) {
b = 0;
}
let g = (num & 0x0000FF) + amount;
if (g > 255) {
g = 255;
} else if (g < 0) {
g = 0;
}
let color = (g | (b << 8) | (r << 16)).toString(16);
while (color.length < 6){
color = 0 + color;
}
return (usePound ? '#' : '') + color;
}

I've rewritten this answer https://stackoverflow.com/a/13542669/4537906 into a pair of readable functions in TypeScript.
The reason is that in modern JavaScript we don't need to care about saving characters any more. That is done by the compilers. IMO we should aim for a readable and understandable code.
This is my approach:
tint.ts
type ColorObject = Record<"r" | "g" | "b" | "a", number>;
const singleColorSpace = 16 * 16; // 256
const blueSpace = singleColorSpace;
const greenSpace = blueSpace * singleColorSpace; // 65536
const redSpace = greenSpace * singleColorSpace; // 16777216
/* eslint-disable regex/invalid */
// adapted to TS from https://github.com/PimpTrizkit/PJs/wiki/12.-Shade,-Blend-and-Convert-a-Web-Color-(pSBC.js)
export const toColorObject = (rgbOrHex: string): ColorObject => {
const { length } = rgbOrHex;
const outputColor = {} as ColorObject;
if (length > 9) {
const rgbaColor = rgbOrHex.split(",");
const [rgbaAndRed, green, blue, alpha] = rgbaColor;
if (rgbaAndRed.slice(0, 3) !== "rgb") {
throw new Error("Invalid color format");
}
const red = rgbaAndRed[3] === "a" ? rgbaAndRed.slice(5) : rgbaAndRed.slice(4);
const rgbaLength = rgbaColor.length;
if (rgbaLength < 3 || rgbaLength > 4) {
return null;
}
outputColor.r = parseInt(red, 10);
outputColor.g = parseInt(green, 10);
outputColor.b = parseInt(blue, 10);
outputColor.a = alpha ? parseFloat(alpha) : -1;
} else {
if (length === 8 || length === 6 || length < 4) {
throw new Error("Invalid hex color format");
}
let HexColor = rgbOrHex;
if (length < 6) {
HexColor = `#${rgbOrHex[1]}${rgbOrHex[1]}${rgbOrHex[2]}${rgbOrHex[2]}${rgbOrHex[3]}${rgbOrHex[3]}${
length > 4 ? rgbOrHex[4] + rgbOrHex[4] : ""
}`;
}
if (length === 9 || length === 5) {
const hexRed = parseInt(HexColor.slice(1, 3), 16);
outputColor.r = hexRed;
const hexGreen = parseInt(HexColor.slice(3, 5), 16);
outputColor.g = hexGreen;
const hexBlue = parseInt(HexColor.slice(5, 7), 16);
outputColor.b = hexBlue;
const hexAlpha = parseInt(HexColor.slice(7, 9), 16);
outputColor.a = Math.round((hexAlpha / 255) * 100) / 100;
} else {
const hexRed = parseInt(HexColor.slice(1, 3), 16);
outputColor.r = hexRed;
const hexGreen = parseInt(HexColor.slice(3, 5), 16);
outputColor.g = hexGreen;
const hexBlue = parseInt(HexColor.slice(5, 7), 16);
outputColor.b = hexBlue;
outputColor.a = -1;
}
}
return outputColor;
};
const black: ColorObject = { r: 0, g: 0, b: 0, a: -1 };
const white: ColorObject = { r: 255, g: 255, b: 255, a: -1 };
export const tint = (
ratio: number,
inputColor: string,
{ toColor, useLinear, reformat }: { toColor?: string; useLinear?: boolean; reformat?: boolean } = {}
) => {
const { round } = Math;
const clampedRatio = Math.min(Math.max(ratio, -1), 1);
if (ratio < -1 || ratio > 1) {
// eslint-disable-next-line no-console
console.info(`Ratio should be between -1 and 1 and it is ${ratio}. It will be clamped to ${clampedRatio}`);
}
let baseColor = inputColor;
if (inputColor[0] !== "r" && inputColor[0] !== "#") {
baseColor = "#000";
// eslint-disable-next-line no-console
console.info(
`Invalid input color format. "${inputColor}" should be rgb(a) or hex. It will fallback to "${baseColor}"`
);
}
let isRGBformat = baseColor.length > 9 || baseColor.includes("rgb(");
isRGBformat = reformat ? !isRGBformat : isRGBformat;
if (toColor) {
const isToColorRgbFormat = (toColor && toColor?.length > 9) || toColor?.includes("rgb(");
isRGBformat = reformat ? !isToColorRgbFormat : isToColorRgbFormat;
}
const formattedBaseColor = toColorObject(baseColor);
const isNegativeRatio = clampedRatio < 0;
const toColorDefault = isNegativeRatio ? black : white;
const formattedToColor = toColor && !reformat ? toColorObject(toColor) : toColorDefault;
const toColorRatio = Math.abs(clampedRatio);
const baseRatio = 1 - toColorRatio;
const outputColor = {} as ColorObject;
if (useLinear) {
outputColor.r = round(baseRatio * formattedBaseColor.r + toColorRatio * formattedToColor.r);
outputColor.g = round(baseRatio * formattedBaseColor.g + toColorRatio * formattedToColor.g);
outputColor.b = round(baseRatio * formattedBaseColor.b + toColorRatio * formattedToColor.b);
} else {
outputColor.r = round((baseRatio * formattedBaseColor.r ** 2 + toColorRatio * formattedToColor.r ** 2) ** 0.5);
outputColor.g = round((baseRatio * formattedBaseColor.g ** 2 + toColorRatio * formattedToColor.g ** 2) ** 0.5);
outputColor.b = round((baseRatio * formattedBaseColor.b ** 2 + toColorRatio * formattedToColor.b ** 2) ** 0.5);
}
const blendedAlpha = formattedBaseColor.a * baseRatio + formattedToColor.a * toColorRatio;
outputColor.a = formattedToColor.a < 0 ? formattedBaseColor.a : blendedAlpha;
const hasAlpha = formattedBaseColor.a >= 0 || formattedToColor.a >= 0;
if (isRGBformat) {
return `rgb${hasAlpha ? "a" : ""}(${outputColor.r},${outputColor.g},${outputColor.b}${
hasAlpha ? `,${round(outputColor.a * 1000) / 1000}` : ""
})`;
}
return `#${(
outputColor.r * redSpace +
outputColor.g * greenSpace +
outputColor.b * blueSpace +
(hasAlpha ? round(outputColor.a * 255) : 0)
)
.toString(16)
// If no Alpha, we remove the last 2 hex digits
.slice(0, hasAlpha ? undefined : -2)}`;
};
And also a collection of jest tests
tint.test.ts
import { tint, toColorObject } from "./tint";
const rgbBlue = "rgb(20,60,200)";
const rgbaBlue = "rgba(20,60,200,0.67423)";
const hex6Cyan = "#67DAF0";
const hex3Pink = "#F3A";
const hex4Pink = "#F3A9";
const rbgBrown = "rgb(200,60,20)";
const rgbaBrown = "rgba(200,60,20,0.98631)";
describe("tint", () => {
describe("Logarithmic blending", () => {
describe("Shades", () => {
it("lightens rgb color", () => {
expect(tint(0.42, rgbBlue)).toEqual("rgb(166,171,225)");
});
it("darkens hex color", () => {
expect(tint(-0.4, hex3Pink)).toEqual("#c62884");
});
it("lightens rgba color", () => {
expect(tint(0.42, rgbaBrown)).toEqual("rgba(225,171,166,0.986)");
});
it("returns black with ratio -1", () => {
expect(tint(-1, rgbBlue)).toEqual("rgb(0,0,0)");
});
});
describe("converts color notation", () => {
it("converts from rgba to hexa", () => {
// expect(tint(0.42, color2, "c")).toEqual("#a6abe1ac");
expect(tint(0.42, rgbaBlue, { reformat: true })).toEqual("#a6abe1ac");
});
it("converts from hexa to rgba", () => {
// expect(tint(0, color6, "c", true)).toEqual("rgba(255,51,170,0.6)");
expect(tint(0, hex4Pink, { reformat: true })).toEqual("rgba(255,51,170,0.6)");
});
it("converts and returns white with ratio 1", () => {
expect(tint(1, hex3Pink, { reformat: true })).toEqual("rgb(255,255,255)");
});
});
describe("Blends two colors", () => {
it("blends rgba with rgba", () => {
expect(tint(-0.5, rgbaBlue, { toColor: rgbaBrown })).toEqual("rgba(142,60,142,0.83)");
});
it("blends rgba with rgb", () => {
expect(tint(0.7, rgbaBlue, { toColor: rbgBrown })).toEqual("rgba(168,60,111,0.674)");
});
it("blends hex with rgb", () => {
expect(tint(0.25, hex6Cyan, { toColor: rbgBrown })).toEqual("rgb(134,191,208)");
});
it("blends rgb with hex", () => {
expect(tint(0.75, rbgBrown, { toColor: hex6Cyan })).toEqual("#86bfd0");
});
});
});
describe("Linear Blending", () => {
describe("Shades", () => {
it("lightens rgb color", () => {
expect(tint(0.42, rgbBlue, { useLinear: true })).toEqual("rgb(119,142,223)");
});
it("darkens hex color", () => {
expect(tint(-0.4, hex3Pink, { useLinear: true })).toEqual("#991f66");
});
it("lightens rgba color", () => {
expect(tint(0.42, rgbaBrown, { useLinear: true })).toEqual("rgba(223,142,119,0.986)");
});
it("returns black with ratio -1", () => {
expect(tint(-1, rgbBlue, { useLinear: true })).toEqual("rgb(0,0,0)");
});
});
describe("converts color notation", () => {
it("converts from rgba to hexa", () => {
expect(tint(0.42, rgbaBlue, { reformat: true, useLinear: true })).toEqual("#778edfac");
});
it("converts from hexa to rgba", () => {
expect(tint(0, hex4Pink, { reformat: true, useLinear: true })).toEqual("rgba(255,51,170,0.6)");
});
it("converts and returns white with ratio 1", () => {
expect(tint(1, hex3Pink, { useLinear: true, reformat: true })).toEqual("rgb(255,255,255)");
});
});
describe("Blends two colors", () => {
it("blends rgba with rgba", () => {
expect(tint(-0.5, rgbaBlue, { toColor: rgbaBrown, useLinear: true })).toEqual("rgba(110,60,110,0.83)");
});
it("blends rgba with rgb", () => {
expect(tint(0.7, rgbaBlue, { toColor: rbgBrown, useLinear: true })).toEqual("rgba(146,60,74,0.674)");
});
it("blends hex with rgb", () => {
expect(tint(0.25, hex6Cyan, { toColor: rbgBrown, useLinear: true })).toEqual("rgb(127,179,185)");
});
it("blends rgb with hex", () => {
expect(tint(0.75, rbgBrown, { toColor: hex6Cyan, useLinear: true })).toEqual("#7fb3b9");
});
});
});
describe("Error handling", () => {
describe("When invalid hex color provided", () => {
it.each([1, 2, 5])("throws error if hex color has %s characters", (n) => {
const correlativeNumbers = Array.from(Array(n).keys()).join("");
expect(() => tint(0, `#${correlativeNumbers}`)).toThrow("Invalid hex color format");
});
});
describe("When ratio is not between -1 and 1", () => {
it("clamps ratio to -1", () => {
expect(tint(-43, rgbBlue)).toEqual("rgb(0,0,0)");
});
it("clamps ratio to 1", () => {
expect(tint(42, rgbBlue)).toEqual("rgb(255,255,255)");
});
});
});
});
describe("toColorObject function", () => {
it("should return a color object from hex", () => {
expect(toColorObject("#fff")).toEqual({
r: 255,
g: 255,
b: 255,
a: -1,
});
});
it("should return a color object from hex with alpha", () => {
expect(toColorObject("#fff6")).toEqual({
r: 255,
g: 255,
b: 255,
a: 0.4,
});
});
it("should return a color object from rgb", () => {
expect(toColorObject("rgb(255,255,255)")).toEqual({
r: 255,
g: 255,
b: 255,
a: -1,
});
});
it("should return a color object from rgba", () => {
expect(toColorObject("rgba(255,255,255,1)")).toEqual({
r: 255,
g: 255,
b: 255,
a: 1,
});
});
describe("Error handling", () => {
it("should throw error if invalid color provided", () => {
expect(() => toColorObject("foo")).toThrow("Invalid hex color format");
});
it("should throw error if invalid color provided", () => {
expect(() => toColorObject("invalid color")).toThrow("Invalid color format");
});
});
});
I hope you enjoy it. It's very simple but it works pretty fine

I made a port of the excellent xcolor library to remove its jQuery dependency. There are a ton of functions in there including lightening and darkening colors.
Really, converting hex to RGB is a completely separate function from lightening or darkening colors. Keep things DRY please. In any case, once you have an RGB color, you can just add the difference between the light level you want and the light level you have to each of the RGB values:
var lightness = function(level) {
if(level === undefined) {
return Math.max(this.g,this.r,this.b)
} else {
var roundedLevel = Math.round(level) // fractions won't work here
var levelChange = roundedLevel - this.lightness()
var r = Math.max(0,this.r+levelChange)
var g = Math.max(0,this.g+levelChange)
var b = Math.max(0,this.b+levelChange)
if(r > 0xff) r = 0xff
if(g > 0xff) g = 0xff
if(b > 0xff) b = 0xff
return xolor({r: r, g: g, b: b})
}
}
var lighter = function(amount) {
return this.lightness(this.lightness()+amount)
}
See https://github.com/fresheneesz/xolor for more of the source.

I've long wanted to be able to produce tints/shades of colours, here is my JavaScript solution:
const varyHue = function (hueIn, pcIn) {
const truncate = function (valIn) {
if (valIn > 255) {
valIn = 255;
} else if (valIn < 0) {
valIn = 0;
}
return valIn;
};
let red = parseInt(hueIn.substring(0, 2), 16);
let green = parseInt(hueIn.substring(2, 4), 16);
let blue = parseInt(hueIn.substring(4, 6), 16);
let pc = parseInt(pcIn, 10); //shade positive, tint negative
let max = 0;
let dif = 0;
max = red;
if (pc < 0) { //tint: make lighter
if (green < max) {
max = green;
}
if (blue < max) {
max = blue;
}
dif = parseInt(((Math.abs(pc) / 100) * (255 - max)), 10);
return leftPad(((truncate(red + dif)).toString(16)), '0', 2) + leftPad(((truncate(green + dif)).toString(16)), '0', 2) + leftPad(((truncate(blue + dif)).toString(16)), '0', 2);
} else { //shade: make darker
if (green > max) {
max = green;
}
if (blue > max) {
max = blue;
}
dif = parseInt(((pc / 100) * max), 10);
return leftPad(((truncate(red - dif)).toString(16)), '0', 2) + leftPad(((truncate(green - dif)).toString(16)), '0', 2) + leftPad(((truncate(blue - dif)).toString(16)), '0', 2);
}
};

thank all for all good answer and edits, they help me to I develop this helpful solution, as I think.
/**
* #param color Hex value format: #ffffff or ffffff or fff or ffffff00 with opacity or rgb rgba formats
* #param decimal lighten or darken decimal value, example -0.5 to darken by 50% or 0.5 to lighten by 50%.
*/
function shadeColor(color, decimal){
if(typeof color!=="string" || !color)return color;
if(typeof decimal !== "number" || !decimal)return color;
var r,g,b;
var _format="";
color=color.trim();
if(/^rgba?\((\s*[0-9]{1,3}\s*)(,\s*[0-9]{1,3}\s*){2}(,\s*[0-9](\.[0-9]+)?\s*)?\)$/i.test(color)){
color=color.replace(/[a-z)(\s]+/ig,'').split(/,/)
.map(function (c,i) { return ((i===3)?c:parseInt(c)); });
_format="rgb";
if(color.length===4) {
_format += "a(VAL," + color[3]+")";
color.pop();
}else _format+="(VAL)";
}else if(/^#?([a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})$/i.test(color)){
var group=color.length>5?2:1;
color=color.replace("#","")
.split(new RegExp("([a-f0-9]{"+group.toString()+"})",'i'))
.filter(function(c){return c.length===group;})
.map(function (c,i) { if(group===1)c+=c;return ((i===3)?c:parseInt(c,16)); });
_format="#VAL";
if(color.length===4) {
_format += color[3];
color.pop();
}
}else return color;
if(decimal<=1 && decimal>=-1)decimal*=100;
color=color.reduce(function (_c,c) {
c=Math.round(c * (100 + decimal) / 100);
c= (c < 255)? c : 255;
if(/^#/.test(_format)){
c=c.toString(16);
if(c.length === 1)c="0"+c;
return _c+c;
}
if(_c==="")return c;
return _c+","+c;
},"");
return _format.replace("VAL",color);
}
You Can Use to dark or light, example:
//lighten
shadeColor("#abcdef",0.5);
shadeColor("#abcdef",0.3);
shadeColor("#abcdef",50);
shadeColor("#abcdef",30);
//it's work with all color format supported in css3 and transparent future
//for example transparnet
shadeColor("#abc8",0.5);
shadeColor("#abcdefff",0.5);
shadeColor("#abcf",35);
//for example rgb and rgba
shadeColor("rgb(128,33,244)",0.5);
shadeColor("rgba(128,33,244,0.3)",0.5);
//darken like above except the decimal with negative number (less than 0)
shadeColor("#abcdef",-0.5);
shadeColor("#abcdef",-50);
shadeColor("rgb(128,33,244)",-35);

How to simple shade color in PHP?
<?php
function shadeColor ($color='#cccccc', $percent=-25) {
$color = Str_Replace("#",Null,$color);
$r = Hexdec(Substr($color,0,2));
$g = Hexdec(Substr($color,2,2));
$b = Hexdec(Substr($color,4,2));
$r = (Int)($r*(100+$percent)/100);
$g = (Int)($g*(100+$percent)/100);
$b = (Int)($b*(100+$percent)/100);
$r = Trim(Dechex(($r<255)?$r:255));
$g = Trim(Dechex(($g<255)?$g:255));
$b = Trim(Dechex(($b<255)?$b:255));
$r = ((Strlen($r)==1)?"0{$r}":$r);
$g = ((Strlen($g)==1)?"0{$g}":$g);
$b = ((Strlen($b)==1)?"0{$b}":$b);
return (String)("#{$r}{$g}{$b}");
}
echo shadeColor(); // #999999

Related

Why does not JS background colour of input field check work? [duplicate]

Using the following jQuery will get the RGB value of an element's background color:
$('#selector').css('backgroundColor');
Is there a way to get the hex value rather than the RGB?
TL;DR
Use this clean one-line function with both rgb and rgba support:
const rgba2hex = (rgba) => `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}`
2021 updated answer
Much time has passed since I originally answered this question. Then cool ECMAScript 5 and 2015+ features became largely available on browsers, like arrow functions, Array.map, String.padStart and template strings. Since some years, it's possible to write cross-browser one-liner rgb2hex:
const rgb2hex = (rgb) => `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('')}`
// Use as you wish...
console.log(rgb2hex('rgb(0,0,0)'))
console.log(rgb2hex('rgb(255, 255, 255)'))
console.log(rgb2hex('rgb(255,0,0)'))
console.log(rgb2hex('rgb(38, 170, 90)'))
It works by using a regular expression to get each digit inside the rgb string, slice(1) to get only the digits (the first result of match is the full string itself), map to iterate through each digit, each iteration converting to Number with parseInt, then back to an hexadecimal String (through a base-16 conversion), adding zero if needed via padStart. Finally, join each converted/adjusted digit to a unique String starting with '#'.
Of course, we could extend it without much effort as an one-liner rgba2hex:
const rgba2hex = (rgba) => `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}`
// Now it doesn't matter if 'rgb' or 'rgba'...
console.log(rgba2hex('rgb(0,0,0)'))
console.log(rgba2hex('rgb(255, 255, 255)'))
console.log(rgba2hex('rgb(255,0,0)'))
console.log(rgba2hex('rgb(38, 170, 90)'))
console.log(rgba2hex('rgba(255, 0, 0, 0.5)'))
console.log(rgba2hex('rgba(0,255,0,1)'))
console.log(rgba2hex('rgba(127,127,127,0.25)'))
And that's it. But if you want to dive deep in the old school JavaScript world, keep reading.
Original 2010 answer
Here is the cleaner solution I wrote based on #Matt suggestion:
function rgb2hex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
Some browsers already returns colors as hexadecimal (as of Internet Explorer 8 and below). If you need to deal with those cases, just append a condition inside the function, like #gfrobenius suggested:
function rgb2hex(rgb) {
if (/^#[0-9A-F]{6}$/i.test(rgb)) return rgb;
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
If you're using jQuery and want a more complete approach, you can use CSS Hooks available since jQuery 1.4.3, as I showed when answering this question: Can I force jQuery.css("backgroundColor") returns on hexadecimal format?
var hexDigits = new Array
("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
//Function to convert rgb color to hex format
function rgb2hex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
function hex(x) {
return isNaN(x) ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16];
}
(Source)
Most browsers seem to return the RGB value when using:
$('#selector').css('backgroundColor');
Only I.E (only 6 tested so far) returns the Hex value.
To avoid error messages in I.E, you could wrap the function in an if statement:
function rgb2hex(rgb) {
if ( rgb.search("rgb") == -1 ) {
return rgb;
} else {
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
}
Updated #ErickPetru for rgba compatibility:
function rgb2hex(rgb) {
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
I updated the regex to match the alpha value if defined, but not use it.
Here's an ES6 one liner that doesn't use jQuery:
var rgb = document.querySelector('#selector').style['background-color'];
return '#' + rgb.substr(4, rgb.indexOf(')') - 4).split(',').map((color) => parseInt(color).toString(16).padStart(2, '0')).join('');
Here is a version that also checks for transparent, I needed this since my object was to insert the result into a style attribute, where the transparent version of a hex color is actually the word "transparent"..
function rgb2hex(rgb) {
if ( rgb.search("rgb") == -1 ) {
return rgb;
}
else if ( rgb == 'rgba(0, 0, 0, 0)' ) {
return 'transparent';
}
else {
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
}
Short
// c - color str e.g."rgb(12,233,43)", result color hex e.g. "#0ce92b"
let rgb2hex=c=>'#'+c.match(/\d+/g).map(x=>(+x).toString(16).padStart(2,0)).join``
// rgb - color str e.g."rgb(12,233,43)", result color hex e.g. "#0ce92b"
let rgb2hex= c=> '#'+c.match(/\d+/g).map(x=>(+x).toString(16).padStart(2,0)).join``
console.log(rgb2hex("rgb(12,233,43"));
Function that returns background color of an element in hex.
function getBgColorHex(elem){
var color = elem.css('background-color')
var hex;
if(color.indexOf('#')>-1){
//for IE
hex = color;
} else {
var rgb = color.match(/\d+/g);
hex = '#'+ ('0' + parseInt(rgb[0], 10).toString(16)).slice(-2) + ('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) + ('0' + parseInt(rgb[2], 10).toString(16)).slice(-2);
}
return hex;
}
usage example:
$('#div1').click(function(){
alert(getBgColorHex($(this));
}
jsfiddle
Readable && Reg-exp free (no Reg-exp)
I've created a function that uses readable basic functions and no reg-exps.
The function accepts color in hex, rgb or rgba CSS format and returns hex representation.
EDIT: there was a bug with parsing out rgba() format, fixed...
function getHexColor( color ){
//if color is already in hex, just return it...
if( color.indexOf('#') != -1 ) return color;
//leave only "R,G,B" :
color = color
.replace("rgba", "") //must go BEFORE rgb replace
.replace("rgb", "")
.replace("(", "")
.replace(")", "");
color = color.split(","); // get Array["R","G","B"]
// 0) add leading #
// 1) add leading zero, so we get 0XY or 0X
// 2) append leading zero with parsed out int value of R/G/B
// converted to HEX string representation
// 3) slice out 2 last chars (get last 2 chars) =>
// => we get XY from 0XY and 0X stays the same
return "#"
+ ( '0' + parseInt(color[0], 10).toString(16) ).slice(-2)
+ ( '0' + parseInt(color[1], 10).toString(16) ).slice(-2)
+ ( '0' + parseInt(color[2], 10).toString(16) ).slice(-2);
}
Same answer like #Jim F answer but ES6 syntax , so, less instructions :
const rgb2hex = (rgb) => {
if (rgb.search("rgb") === -1) return rgb;
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/);
const hex = (x) => ("0" + parseInt(x).toString(16)).slice(-2);
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
};
color class taken from bootstrap color picker
// Color object
var Color = function(val) {
this.value = {
h: 1,
s: 1,
b: 1,
a: 1
};
this.setColor(val);
};
Color.prototype = {
constructor: Color,
//parse a string to HSB
setColor: function(val){
val = val.toLowerCase();
var that = this;
$.each( CPGlobal.stringParsers, function( i, parser ) {
var match = parser.re.exec( val ),
values = match && parser.parse( match ),
space = parser.space||'rgba';
if ( values ) {
if (space === 'hsla') {
that.value = CPGlobal.RGBtoHSB.apply(null, CPGlobal.HSLtoRGB.apply(null, values));
} else {
that.value = CPGlobal.RGBtoHSB.apply(null, values);
}
return false;
}
});
},
setHue: function(h) {
this.value.h = 1- h;
},
setSaturation: function(s) {
this.value.s = s;
},
setLightness: function(b) {
this.value.b = 1- b;
},
setAlpha: function(a) {
this.value.a = parseInt((1 - a)*100, 10)/100;
},
// HSBtoRGB from RaphaelJS
// https://github.com/DmitryBaranovskiy/raphael/
toRGB: function(h, s, b, a) {
if (!h) {
h = this.value.h;
s = this.value.s;
b = this.value.b;
}
h *= 360;
var R, G, B, X, C;
h = (h % 360) / 60;
C = b * s;
X = C * (1 - Math.abs(h % 2 - 1));
R = G = B = b - C;
h = ~~h;
R += [C, X, 0, 0, X, C][h];
G += [X, C, C, X, 0, 0][h];
B += [0, 0, X, C, C, X][h];
return {
r: Math.round(R*255),
g: Math.round(G*255),
b: Math.round(B*255),
a: a||this.value.a
};
},
toHex: function(h, s, b, a){
var rgb = this.toRGB(h, s, b, a);
return '#'+((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1);
},
toHSL: function(h, s, b, a){
if (!h) {
h = this.value.h;
s = this.value.s;
b = this.value.b;
}
var H = h,
L = (2 - s) * b,
S = s * b;
if (L > 0 && L <= 1) {
S /= L;
} else {
S /= 2 - L;
}
L /= 2;
if (S > 1) {
S = 1;
}
return {
h: H,
s: S,
l: L,
a: a||this.value.a
};
}
};
how to use
var color = new Color("RGB(0,5,5)");
color.toHex()
Just to add to #Justin's answer above..
it should be
var rgb = document.querySelector('#selector').style['background-color'];
return '#' + rgb.substr(4, rgb.indexOf(')') - 4).split(',').map((color) => String("0" + parseInt(color).toString(16)).slice(-2)).join('');
As the above parse int functions truncates leading zeroes, thus produces incorrect color codes of 5 or 4 letters may be... i.e. for rgb(216, 160, 10) it produces #d8a0a while it should be #d8a00a.
Thanks
This one looks a bit nicer:
var rgb = $('#selector').css('backgroundColor').match(/\d+/g);
var r = parseInt(rgb[0], 10);
var g = parseInt(rgb[1], 10);
var b = parseInt(rgb[2], 10);
var hex = '#'+ r.toString(16) + g.toString(16) + b.toString(16);
a more succinct one-liner:
var rgb = $('#selector').css('backgroundColor').match(/\d+/g);
var hex = '#'+ Number(rgb[0]).toString(16) + Number(rgb[1]).toString(16) + Number(rgb[2]).toString(16);
forcing jQuery to always return hex:
$.cssHooks.backgroundColor = {
get: function(elem) {
if (elem.currentStyle)
var bg = elem.currentStyle["backgroundColor"];
else if (window.getComputedStyle) {
var bg = document.defaultView.getComputedStyle(elem,
null).getPropertyValue("background-color");
}
if (bg.search("rgb") == -1) {
return bg;
} else {
bg = bg.match(/\d+/g);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(bg[0]) + hex(bg[1]) + hex(bg[2]);
}
}
}
Since the question was using JQuery, here’s a JQuery plugin based on Daniel Elliott’s code:
$.fn.cssAsHex = function(colorProp) {
var hexDigits = '0123456789abcdef';
function hex(x) {
return isNaN(x) ? '00' : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16];
};
// Convert RGB color to Hex format
function rgb2hex(rgb) {
var rgbRegex = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
return '#' + hex(rgbRegex[1]) + hex(rgbRegex[2]) + hex(rgbRegex[3]);
};
return rgb2hex(this.css(colorProp));
};
Use it like:
var hexBackgroundColor = $('#myElement').cssAsHex('background-color');
Here's a solution I found that does not throw scripting errors in IE:
http://haacked.com/archive/2009/12/29/convert-rgb-to-hex.aspx
Steven Pribilinskiy's answer drops leading zeroes, for example #ff0000 becomes #ff00.
A solution is to append a leading 0 and substring off the last 2 digits.
var rgb = $('#selector').css('backgroundColor').match(/\d+/g);
var hex = '#'+ String('0' + Number(rgb[0]).toString(16)).slice(-2) + String('0' + Number(rgb[1]).toString(16)).slice(-2) + String('0' + Number(rgb[2]).toString(16)).slice(-2);
Improved function "hex"
function hex(x){
return isNaN(x) ? "00" : hexDigits[x >> 4] + hexDigits[x & 0xf];
// or option without hexDigits array
return (x >> 4).toString(16)+(x & 0xf).toString(16);
}
Here is my solution, also does touppercase by the use of an argument and checks for other possible white-spaces and capitalisation in the supplied string.
var a = "rgb(10, 128, 255)";
var b = "rgb( 10, 128, 255)";
var c = "rgb(10, 128, 255 )";
var d = "rgb ( 10, 128, 255 )";
var e = "RGB ( 10, 128, 255 )";
var f = "rgb(10,128,255)";
var g = "rgb(10, 128,)";
var rgbToHex = (function () {
var rx = /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
function pad(num) {
if (num.length === 1) {
num = "0" + num;
}
return num;
}
return function (rgb, uppercase) {
var rxArray = rgb.match(rx),
hex;
if (rxArray !== null) {
hex = pad(parseInt(rxArray[1], 10).toString(16)) + pad(parseInt(rxArray[2], 10).toString(16)) + pad(parseInt(rxArray[3], 10).toString(16));
if (uppercase === true) {
hex = hex.toUpperCase();
}
return hex;
}
return;
};
}());
console.log(rgbToHex(a));
console.log(rgbToHex(b, true));
console.log(rgbToHex(c));
console.log(rgbToHex(d));
console.log(rgbToHex(e));
console.log(rgbToHex(f));
console.log(rgbToHex(g));
On jsfiddle
Speed comparison on jsperf
A further improvement could be to trim() the rgb string
var rxArray = rgb.trim().match(rx),
My beautiful non-standard solution
HTML
<div id="selector" style="background-color:#f5b405"></div>
jQuery
$("#selector").attr("style").replace("background-color:", "");
Result
#f5b405
Convert RGB to Hex
I am using Jasmine protractor and I was getting errors like
- Expected [ 'rgba(255, 255, 255, 1)' ] to contain '#fff'.
Below function worked fine for me.
function RGBAToHexA(test:string) {
let sep = test.toString().indexOf(",") > -1 ? "," : " ";
const rgba = test.toString().substring(5).split(")")[0].split(sep);
console.log(rgba)
let r = (+rgba[0]).toString(16),
g = (+rgba[1]).toString(16),
b = (+rgba[2]).toString(16),
a = Math.round(+rgba[3] * 255).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
if (a.length == 1)
a = "0" + a;
return "#" + r + g + b + a;
}
describe('Check CSS', function() {
it('should check css of login page', async function(){
browser.waitForAngularEnabled(true);
browser.actions().mouseMove(element(by.css('.btn-auth, .btn-auth:hover'))).perform(); // mouse hover on button
csspage.Loc_auth_btn.getCssValue('color').then(function(color){
console.log(RGBAToHexA(color))
expect( RGBAToHexA(color)).toContain(cssData.hoverauth.color);
})
To all the Functional Programming lovers, here is a somewhat functional approach :)
const getHexColor = (rgbValue) =>
rgbValue.replace("rgb(", "").replace(")", "").split(",")
.map(colorValue => (colorValue > 15 ? "0" : "") + colorValue.toString(16))
.reduce((acc, hexValue) => acc + hexValue, "#")
then use it like:
const testRGB = "rgb(13,23,12)"
getHexColor(testRGB)
my compact version
//Function to convert rgb color to hex format
function rgb2hex(rgb) {
if(/^#/.test(rgb))return rgb;// if returns colors as hexadecimal
let re = /\d+/g;
let hex = x => (x >> 4).toString(16)+(x & 0xf).toString(16);
return "#"+hex(re.exec(rgb))+hex(re.exec(rgb))+hex(re.exec(rgb));
}
full cases (rgb, rgba, transparent...etc) solution (coffeeScript)
rgb2hex: (rgb, transparentDefault=null)->
return null unless rgb
return rgb if rgb.indexOf('#') != -1
return transparentDefault || 'transparent' if rgb == 'rgba(0, 0, 0, 0)'
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/);
hex = (x)->
("0" + parseInt(x).toString(16)).slice(-2)
'#' + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3])
Hi here's my solution after getting the element's color with Javascript
function rgb_hex(rgb_string_js){ //example: "rgb(102,54,255)"
var new_rgb_list = rgb_string_js.replace("rgb(","").replace(")","").split(",");
return ("#" + parseInt(new_rgb_list[0]).toString(16) + parseInt(new_rgb_list[1]).toString(16) + parseInt(new_rgb_list[2]).toString(16));
}

Trying to get a lighter color from hex value [duplicate]

Here is a function I was working on to programmatically lighten or darken a hex color by a specific amount. Just pass in a string like "3F6D2A" for the color (col) and a base10 integer (amt) for the amount to lighten or darken. To darken, pass in a negative number (i.e. -20).
The reason for me to do this was because of all the solutions I found, thus far, they seemed to over-complicate the issue. And I had a feeling it could be done with just a couple lines of code. Please let me know if you find any problems, or have any adjustments to make that would speed it up.
function LightenDarkenColor(col, amt) {
col = parseInt(col, 16);
return (((col & 0x0000FF) + amt) | ((((col >> 8) & 0x00FF) + amt) << 8) | (((col >> 16) + amt) << 16)).toString(16);
}
// TEST
console.log( LightenDarkenColor("3F6D2A",40) );
For Development use here is an easier to read version:
function LightenDarkenColor(col, amt) {
var num = parseInt(col, 16);
var r = (num >> 16) + amt;
var b = ((num >> 8) & 0x00FF) + amt;
var g = (num & 0x0000FF) + amt;
var newColor = g | (b << 8) | (r << 16);
return newColor.toString(16);
}
// TEST
console.log(LightenDarkenColor("3F6D2A", -40));
And finally a version to handle colors that may (or may not) have the "#" in the beginning. Plus adjusting for improper color values:
function LightenDarkenColor(col,amt) {
var usePound = false;
if ( col[0] == "#" ) {
col = col.slice(1);
usePound = true;
}
var num = parseInt(col,16);
var r = (num >> 16) + amt;
if ( r > 255 ) r = 255;
else if (r < 0) r = 0;
var b = ((num >> 8) & 0x00FF) + amt;
if ( b > 255 ) b = 255;
else if (b < 0) b = 0;
var g = (num & 0x0000FF) + amt;
if ( g > 255 ) g = 255;
else if ( g < 0 ) g = 0;
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
}
OK, so now it's not just a couple of lines, but it seems far simpler and if you're not using the "#" and don't need to check for colors out of range, it is only a couple of lines.
If not using the "#", you can just add it in code like:
var myColor = "3F6D2A";
myColor = LightenDarkenColor(myColor,10);
thePlaceTheColorIsUsed = ("#" + myColor);
I guess my main question is, am I correct here? Does this not encompass most (normal) situations? And if so, what is the fastest and smallest way to do this? I want to use in animations and in a small environment, so speed is the first most important factor here, size second, accuracy third, readability? huh? not on the list of requirements (sorry, I know half of you are tearing out your eyes right now!).
Well, this answer has become its own beast. Many new versions, it was getting stupid long. Many thanks to all of the great many contributors to this answer. But, in order to keep it simple for the masses. I archived all the versions/history of this answer's evolution to my github. And started it over clean on StackOverflow here with the newest version. A special thanks goes out to Mike 'Pomax' Kamermans for this version. He gave me the new math.
This function (pSBC) will take a HEX or RGB web color. pSBC can shade it darker or lighter, or blend it with a second color, and can also pass it right thru but convert from Hex to RGB (Hex2RGB) or RGB to Hex (RGB2Hex). All without you even knowing what color format you are using.
This runs really fast, probably the fastest, especially considering its many features. It was a long time in the making. See the whole story on my github. If you want the absolutely smallest and fastest possible way to shade or blend, see the Micro Functions below and use one of the 2-liner speed demons. They are great for intense animations, but this version here is fast enough for most animations.
This function uses Log Blending or Linear Blending. However, it does NOT convert to HSL to properly lighten or darken a color. Therefore, results from this function will differ from those much larger and much slower functions that use HSL.
jsFiddle with pSBC
github > pSBC Wiki
Features:
Auto-detects and accepts standard Hex colors in the form of strings. For example: "#AA6622" or "#bb551144".
Auto-detects and accepts standard RGB colors in the form of strings. For example: "rgb(123,45,76)" or "rgba(45,15,74,0.45)".
Shades colors to white or black by percentage.
Blends colors together by percentage.
Does Hex2RGB and RGB2Hex conversion at the same time, or solo.
Accepts 3 digit (or 4 digit w/ alpha) HEX color codes, in the form #RGB (or #RGBA). It will expand them. For Example: "#C41" becomes "#CC4411".
Accepts and (Linear) blends alpha channels. If either the c0 (from) color or the c1 (to) color has an alpha channel, then the returned color will have an alpha channel. If both colors have an alpha channel, then the returned color will be a linear blend of the two alpha channels using the percentage given (just as if it were a normal color channel). If only one of the two colors has an alpha channel, this alpha will just be passed thru to the returned color. This allows one to blend/shade a transparent color while maintaining the transparency level. Or, if the transparency levels should blend as well, make sure both colors have alphas. When shading, it will pass the alpha channel straight thru. If you want basic shading that also shades the alpha channel, then use rgb(0,0,0,1) or rgb(255,255,255,1) as your c1 (to) color (or their hex equivalents). For RGB colors, the returned color's alpha channel will be rounded to 3 decimal places.
RGB2Hex and Hex2RGB conversions are implicit when using blending. Regardless of the c0 (from) color; the returned color will always be in the color format of the c1 (to) color, if one exists. If there is no c1 (to) color, then pass 'c' in as the c1 color and it will shade and convert whatever the c0 color is. If conversion only is desired, then pass 0 in as the percentage (p) as well. If the c1 color is omitted or a non-string is passed in, it will not convert.
A secondary function is added to the global as well. pSBCr can be passed a Hex or RGB color and it returns an object containing this color information. Its in the form: {r: XXX, g: XXX, b: XXX, a: X.XXX}. Where .r, .g, and .b have range 0 to 255. And when there is no alpha: .a is -1. Otherwise: .a has range 0.000 to 1.000.
For RGB output, it outputs rgba() over rgb() when a color with an alpha channel was passed into c0 (from) and/or c1 (to).
Minor Error Checking has been added. It's not perfect. It can still crash or create jibberish. But it will catch some stuff. Basically, if the structure is wrong in some ways or if the percentage is not a number or out of scope, it will return null. An example: pSBC(0.5,"salt") == null, where as it thinks #salt is a valid color. Delete the four lines which end with return null; to remove this feature and make it faster and smaller.
Uses Log Blending. Pass true in for l (the 4th parameter) to use Linear Blending.
Code:
// Version 4.0
const pSBC=(p,c0,c1,l)=>{
let r,g,b,P,f,t,h,i=parseInt,m=Math.round,a=typeof(c1)=="string";
if(typeof(p)!="number"||p<-1||p>1||typeof(c0)!="string"||(c0[0]!='r'&&c0[0]!='#')||(c1&&!a))return null;
if(!this.pSBCr)this.pSBCr=(d)=>{
let n=d.length,x={};
if(n>9){
[r,g,b,a]=d=d.split(","),n=d.length;
if(n<3||n>4)return null;
x.r=i(r[3]=="a"?r.slice(5):r.slice(4)),x.g=i(g),x.b=i(b),x.a=a?parseFloat(a):-1
}else{
if(n==8||n==6||n<4)return null;
if(n<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(n>4?d[4]+d[4]:"");
d=i(d.slice(1),16);
if(n==9||n==5)x.r=d>>24&255,x.g=d>>16&255,x.b=d>>8&255,x.a=m((d&255)/0.255)/1000;
else x.r=d>>16,x.g=d>>8&255,x.b=d&255,x.a=-1
}return x};
h=c0.length>9,h=a?c1.length>9?true:c1=="c"?!h:false:h,f=this.pSBCr(c0),P=p<0,t=c1&&c1!="c"?this.pSBCr(c1):P?{r:0,g:0,b:0,a:-1}:{r:255,g:255,b:255,a:-1},p=P?p*-1:p,P=1-p;
if(!f||!t)return null;
if(l)r=m(P*f.r+p*t.r),g=m(P*f.g+p*t.g),b=m(P*f.b+p*t.b);
else r=m((P*f.r**2+p*t.r**2)**0.5),g=m((P*f.g**2+p*t.g**2)**0.5),b=m((P*f.b**2+p*t.b**2)**0.5);
a=f.a,t=t.a,f=a>=0||t>=0,a=f?a<0?t:t<0?a:a*P+t*p:0;
if(h)return"rgb"+(f?"a(":"(")+r+","+g+","+b+(f?","+m(a*1000)/1000:"")+")";
else return"#"+(4294967296+r*16777216+g*65536+b*256+(f?m(a*255):0)).toString(16).slice(1,f?undefined:-2)
}
Usage:
// Setup:
let color1 = "rgb(20,60,200)";
let color2 = "rgba(20,60,200,0.67423)";
let color3 = "#67DAF0";
let color4 = "#5567DAF0";
let color5 = "#F3A";
let color6 = "#F3A9";
let color7 = "rgb(200,60,20)";
let color8 = "rgba(200,60,20,0.98631)";
// Tests:
/*** Log Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1 ); // rgb(20,60,200) + [42% Lighter] => rgb(166,171,225)
pSBC ( -0.4, color5 ); // #F3A + [40% Darker] => #c62884
pSBC ( 0.42, color8 ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(225,171,166,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c" ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #a6abe1ac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c" ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8 ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(142,60,142,0.83)
pSBC ( 0.7, color2, color7 ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(168,60,111,0.67423)
pSBC ( 0.25, color3, color7 ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(134,191,208)
pSBC ( 0.75, color7, color3 ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #86bfd0
/*** Linear Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1, false, true ); // rgb(20,60,200) + [42% Lighter] => rgb(119,142,223)
pSBC ( -0.4, color5, false, true ); // #F3A + [40% Darker] => #991f66
pSBC ( 0.42, color8, false, true ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(223,142,119,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c", true ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #778edfac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c", true ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8, true ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(110,60,110,0.83)
pSBC ( 0.7, color2, color7, true ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(146,60,74,0.67423)
pSBC ( 0.25, color3, color7, true ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(127,179,185)
pSBC ( 0.75, color7, color3, true ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #7fb3b9
/*** Other Stuff ***/
// Error Checking
pSBC ( 0.42, "#FFBAA" ); // #FFBAA + [42% Lighter] => null  (Invalid Input Color)
pSBC ( 42, color1, color5 ); // rgb(20,60,200) + #F3A + [4200% Blend] => null  (Invalid Percentage Range)
pSBC ( 0.42, {} ); // [object Object] + [42% Lighter] => null  (Strings Only for Color)
pSBC ( "42", color1 ); // rgb(20,60,200) + ["42"] => null  (Numbers Only for Percentage)
pSBC ( 0.42, "salt" ); // salt + [42% Lighter] => null  (A Little Salt is No Good...)
// Error Check Fails (Some Errors are not Caught)
pSBC ( 0.42, "#salt" ); // #salt + [42% Lighter] => #a5a5a500  (...and a Pound of Salt is Jibberish)
// Ripping
pSBCr ( color4 ); // #5567DAF0 + [Rip] => [object Object] => {'r':85,'g':103,'b':218,'a':0.941}
The picture below will help show the difference in the two blending methods:
Micro Functions
If you really want speed and size, you will have to use RGB not HEX. RGB is more straightforward and simple, HEX writes too slow and comes in too many flavors for a simple two-liner (IE. it could be a 3, 4, 6, or 8 digit HEX code). You will also need to sacrifice some features, no error checking, no HEX2RGB nor RGB2HEX. As well, you will need to choose a specific function (based on its function name below) for the color blending math, and if you want shading or blending. These functions do support alpha channels. And when both input colors have alphas it will Linear Blend them. If only one of the two colors has an alpha, it will pass it straight thru to the resulting color. Below are two liner functions that are incredibly fast and small:
const RGB_Linear_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+i(e[3]=="a"?e.slice(5):e.slice(4))*p)+","+r(i(b)*P+i(f)*p)+","+r(i(c)*P+i(g)*p)+j;
}
const RGB_Linear_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:255*p,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+t)+","+r(i(b)*P+t)+","+r(i(c)*P+t)+(d?","+d:")");
}
const RGB_Log_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+p*i(e[3]=="a"?e.slice(5):e.slice(4))**2)**0.5)+","+r((P*i(b)**2+p*i(f)**2)**0.5)+","+r((P*i(c)**2+p*i(g)**2)**0.5)+j;
}
const RGB_Log_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:p*255**2,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+t)**0.5)+","+r((P*i(b)**2+t)**0.5)+","+r((P*i(c)**2+t)**0.5)+(d?","+d:")");
}
Want more info? Read the full writeup on github.
PT
(P.s. If anyone has the math for another blending method, please share.)
I made a solution that works very nice for me:
function shadeColor(color, percent) {
var R = parseInt(color.substring(1,3),16);
var G = parseInt(color.substring(3,5),16);
var B = parseInt(color.substring(5,7),16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R<255)?R:255;
G = (G<255)?G:255;
B = (B<255)?B:255;
R = Math.round(R)
G = Math.round(G)
B = Math.round(B)
var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));
return "#"+RR+GG+BB;
}
Example Lighten:
shadeColor("#63C6FF",40);
Example Darken:
shadeColor("#63C6FF",-40);
Here is a super simple one liner based on Eric's answer
function adjust(color, amount) {
return '#' + color.replace(/^#/, '').replace(/../g, color => ('0'+Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
}
Examples:
adjust('#ffffff', -20) => "#ebebeb"
adjust('000000', 20) => "#141414"
I am adding my 2 cents here, a satisfyingly small combination of different answers:
const colorShade = (col, amt) => {
col = col.replace(/^#/, '')
if (col.length === 3) col = col[0] + col[0] + col[1] + col[1] + col[2] + col[2]
let [r, g, b] = col.match(/.{2}/g);
([r, g, b] = [parseInt(r, 16) + amt, parseInt(g, 16) + amt, parseInt(b, 16) + amt])
r = Math.max(Math.min(255, r), 0).toString(16)
g = Math.max(Math.min(255, g), 0).toString(16)
b = Math.max(Math.min(255, b), 0).toString(16)
const rr = (r.length < 2 ? '0' : '') + r
const gg = (g.length < 2 ? '0' : '') + g
const bb = (b.length < 2 ? '0' : '') + b
return `#${rr}${gg}${bb}`
}
accepts a color starting with # or not, with 6 characters or 3 characters.
example of use: colorShade('#54b946', -40)
Here is the output of 4 colors with 3 shades lighter and 3 shades darker for each of them (amount is a multiple of 40 here).
This is what I used based on your function. I prefer to use steps over percentage because it's more intuitive for me.
For example, 20% of a 200 blue value is much different than 20% of a 40 blue value.
Anyways, here's my modification, thanks for your original function.
function adjustBrightness(col, amt) {
var usePound = false;
if (col[0] == "#") {
col = col.slice(1);
usePound = true;
}
var R = parseInt(col.substring(0,2),16);
var G = parseInt(col.substring(2,4),16);
var B = parseInt(col.substring(4,6),16);
// to make the colour less bright than the input
// change the following three "+" symbols to "-"
R = R + amt;
G = G + amt;
B = B + amt;
if (R > 255) R = 255;
else if (R < 0) R = 0;
if (G > 255) G = 255;
else if (G < 0) G = 0;
if (B > 255) B = 255;
else if (B < 0) B = 0;
var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));
return (usePound?"#":"") + RR + GG + BB;
}
Based on the, David Sherret and Pablo, answer above converted the solution to safer version for Typescript
/**
* #param color Hex value format: #ffffff or ffffff
* #param decimal lighten or darken decimal value, example 0.5 to lighten by 50% or 1.5 to darken by 50%.
*/
static shadeColor(color: string, decimal: number): string {
const base = color.startsWith('#') ? 1 : 0;
let r = parseInt(color.substring(base, 3), 16);
let g = parseInt(color.substring(base + 2, 5), 16);
let b = parseInt(color.substring(base + 4, 7), 16);
r = Math.round(r / decimal);
g = Math.round(g / decimal);
b = Math.round(b / decimal);
r = (r < 255)? r : 255;
g = (g < 255)? g : 255;
b = (b < 255)? b : 255;
const rr = ((r.toString(16).length === 1)? `0${r.toString(16)}` : r.toString(16));
const gg = ((g.toString(16).length === 1)? `0${g.toString(16)}` : g.toString(16));
const bb = ((b.toString(16).length === 1)? `0${b.toString(16)}` : b.toString(16));
return `#${rr}${gg}${bb}`;
}
I tried your function and there was a little bug: If some final 'r' value is 1 digit only, the result comes up like: 'a0a0a' when the right value is '0a0a0a', for example.
I just quick-fixed it by adding this instead of your return:
var rStr = (r.toString(16).length < 2)?'0'+r.toString(16):r.toString(16);
var gStr = (g.toString(16).length < 2)?'0'+g.toString(16):g.toString(16);
var bStr = (b.toString(16).length < 2)?'0'+b.toString(16):b.toString(16);
return (usePound?"#":"") + rStr + gStr + bStr;
Maybe it's not so nice but it do the work. Great function, BTW. Just what I needed. :)
have you thought about an rgb > hsl conversion? then just move the Luminosity up and down? thats the way I would go.
A quick look for some algorithms got me the following sites.
PHP:
http://serennu.com/colour/rgbtohsl.php
Javascript:
http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
EDIT the above link is no longer valid. You can view git hub for the page source or the gist
Alternatively another StackOverflow question might be a good place to look.
Even though this is not the right choice for the OP the following is an approximation of the code I was originally suggesting. (Assuming you have rgb/hsl conversion functions)
var SHADE_SHIFT_AMOUNT = 0.1;
function lightenShade(colorValue)
{
if(colorValue && colorValue.length >= 6)
{
var redValue = parseInt(colorValue.slice(-6,-4), 16);
var greenValue = parseInt(colorValue.slice(-4,-2), 16);
var blueValue = parseInt(colorValue.slice(-2), 16);
var hsl = rgbToHsl(redValue, greenValue, blueValue);
hsl[2]= Math.min(hsl[2] + SHADE_SHIFT_AMOUNT, 1);
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
}
return null;
}
function darkenShade(colorValue)
{
if(colorValue && colorValue.length >= 6)
{
var redValue = parseInt(colorValue.slice(-6,-4), 16);
var greenValue = parseInt(colorValue.slice(-4,-2), 16);
var blueValue = parseInt(colorValue.slice(-2), 16);
var hsl = rgbToHsl(redValue, greenValue, blueValue);
hsl[2]= Math.max(hsl[2] - SHADE_SHIFT_AMOUNT, 0);
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
}
return null;
}
This assumes:
You have functions hslToRgb and rgbToHsl.
The parameter colorValue is a string in the form #RRGGBB
Although if we are discussing css there is a syntax for specifying hsl/hsla for IE9/Chrome/Firefox.
I wanted to change a color to a specific brightness level - no matter what brightness the color was before - here's a simple JS function that seems to work well, although I'm sure it could be shorter
function setLightPercentage(col: any, p: number) {
const R = parseInt(col.substring(1, 3), 16);
const G = parseInt(col.substring(3, 5), 16);
const B = parseInt(col.substring(5, 7), 16);
const curr_total_dark = (255 * 3) - (R + G + B);
// calculate how much of the current darkness comes from the different channels
const RR = ((255 - R) / curr_total_dark);
const GR = ((255 - G) / curr_total_dark);
const BR = ((255 - B) / curr_total_dark);
// calculate how much darkness there should be in the new color
const new_total_dark = ((255 - 255 * (p / 100)) * 3);
// make the new channels contain the same % of available dark as the old ones did
const NR = 255 - Math.round(RR * new_total_dark);
const NG = 255 - Math.round(GR * new_total_dark);
const NB = 255 - Math.round(BR * new_total_dark);
const RO = ((NR.toString(16).length === 1) ? "0" + NR.toString(16) : NR.toString(16));
const GO = ((NG.toString(16).length === 1) ? "0" + NG.toString(16) : NG.toString(16));
const BO = ((NB.toString(16).length === 1) ? "0" + NB.toString(16) : NB.toString(16));
return "#" + RO + GO + BO;}
Your approach is ok :) I simplify your shortest version a little (for saturation control look here)
(col,amt)=> (+('0x'+col)+amt*0x010101).toString(16).padStart(6,0)
// Similar to OP shortest version, we not have here # and colors range checking
var LightenDarkenColor =
(col,amt) => (+('0x'+col)+amt*0x010101).toString(16).padStart(6,0);
// ------
// TEST
// ------
function update() {
let c= col.value.padEnd(6,'0').slice(0,6);
let color = '#'+LightenDarkenColor(c, +amt.value);
oldColor.innerHTML = 'Old: #'+c;
oldColor.style = `background: #${c}`;
newColor.innerHTML = 'New: '+color
newColor.style = `background: ${color}`;
}
update();
.box{ width: 100px; height: 100px; margin: 10px; display: inline-block}
<input id="col" value="3F6D2A" oninput="update()">
<input id="amt" value="30" oninput="update()"><br>
<div id="oldColor" class="box"></div>
<div id="newColor" class="box"></div>
And version with # and color ranges checking
// # and colors range checking
var LightenDarkenColor =
(col,amt) => '#'+col.slice(1).match(/../g)
.map(x=>(x=+`0x${x}`+amt,x<0?0:(x>255?255:x))
.toString(16).padStart(2,0)).join``;
// ------
// TEST
// ------
function update() {
let c= col.value.padEnd(6,'0').slice(0,7);
let color = LightenDarkenColor(c, +amt.value);
oldColor.innerHTML = 'Old: '+c;
oldColor.style = `background: ${c}`;
newColor.innerHTML = 'New: '+color
newColor.style = `background: ${color}`;
}
update();
.box{ width: 100px; height: 100px; margin: 10px; display: inline-block}
<input id="col" value="#3F6D2A" oninput="update()">
<input id="amt" value="40" oninput="update()"><br>
<div id="oldColor" class="box"></div>
<div id="newColor" class="box"></div>
C# Version...
note that I am getting color strings in this format #FF12AE34, and need to cut out the #FF.
private string GetSmartShadeColorByBase(string s, float percent)
{
if (string.IsNullOrEmpty(s))
return "";
var r = s.Substring(3, 2);
int rInt = int.Parse(r, NumberStyles.HexNumber);
var g = s.Substring(5, 2);
int gInt = int.Parse(g, NumberStyles.HexNumber);
var b = s.Substring(7, 2);
int bInt = int.Parse(b, NumberStyles.HexNumber);
var t = percent < 0 ? 0 : 255;
var p = percent < 0 ? percent*-1 : percent;
int newR = Convert.ToInt32(Math.Round((t - rInt) * p) + rInt);
var newG = Convert.ToInt32(Math.Round((t - gInt) * p) + gInt);
var newB = Convert.ToInt32(Math.Round((t - bInt) * p) + bInt);
return String.Format("#{0:X2}{1:X2}{2:X2}", newR, newG, newB);
}
I just used the hex number preceded by '#'.
var x = 0xf0f0f0;
x=x+0xf00; //set this value as you wish programatically
document.getElementById("heading").style = 'background-color: #'+x.toString(16);
higher the number ..lighter the color
I needed it in C#, it may help .net developers
public static string LightenDarkenColor(string color, int amount)
{
int colorHex = int.Parse(color, System.Globalization.NumberStyles.HexNumber);
string output = (((colorHex & 0x0000FF) + amount) | ((((colorHex >> 0x8) & 0x00FF) + amount) << 0x8) | (((colorHex >> 0xF) + amount) << 0xF)).ToString("x6");
return output;
}
My version written in typescript:
function changeColorLightness(color: number, lightness: number): number {
return (Math.max(0, Math.min(((color & 0xFF0000) / 0x10000) + lightness, 0xFF)) * 0x10000) +
(Math.max(0, Math.min(((color & 0x00FF00) / 0x100) + lightness, 0xFF)) * 0x100) +
(Math.max(0, Math.min(((color & 0x0000FF)) + lightness, 0xFF)));
}
explanation:
export function changeColorLightness(color: number, lightness: number): number {
const r = (color & 0xFF0000) / 0x10**4;
const g = (color & 0x00FF00) / 0x10**2;
const b = (color & 0x0000FF);
const changedR = Math.max(0, Math.min(r + lightness, 0xFF));
const changedG = Math.max(0, Math.min(g + lightness, 0xFF));
const changedB = Math.max(0, Math.min(b + lightness, 0xFF));
return (changedR * 0x10**4) + (changedG * 0x10**2) + changedB;
}
usage:
changeColorLightness(0x00FF00, 0x50);
changeColorLightness(parseInt("#00FF00".replace('#',''), 16), 0x50);
changeColorLightness(0x00FF00, 127.5);
The following method will allow you to lighten or darken the exposure value of a Hexadecimal (Hex) color string:
private static string GetHexFromRGB(byte r, byte g, byte b, double exposure)
{
exposure = Math.Max(Math.Min(exposure, 1.0), -1.0);
if (exposure >= 0)
{
return "#"
+ ((byte)(r + ((byte.MaxValue - r) * exposure))).ToString("X2")
+ ((byte)(g + ((byte.MaxValue - g) * exposure))).ToString("X2")
+ ((byte)(b + ((byte.MaxValue - b) * exposure))).ToString("X2");
}
else
{
return "#"
+ ((byte)(r + (r * exposure))).ToString("X2")
+ ((byte)(g + (g * exposure))).ToString("X2")
+ ((byte)(b + (b * exposure))).ToString("X2");
}
}
For the last parameter value in GetHexFromRGB(), Pass in a double value somewhere between -1 and 1 (-1 is black, 0 is unchanged, 1 is white):
// split color (#e04006) into three strings
var r = Convert.ToByte("e0", 16);
var g = Convert.ToByte("40", 16);
var b = Convert.ToByte("06", 16);
GetHexFromRGB(r, g, b, 0.25); // Lighten by 25%;
There is lack of support for colors starting from 00 ie "#000623" but here is the fix
function lightenDarkenColor(colorCode, amount) {
let usePound = false;
if (colorCode[0] == "#") {
colorCode = colorCode.slice(1);
usePound = true;
}
const num = parseInt(colorCode, 16);
let r = (num >> 16) + amount;
if (r > 255) {
r = 255;
} else if (r < 0) {
r = 0;
}
let b = ((num >> 8) & 0x00FF) + amount;
if (b > 255) {
b = 255;
} else if (b < 0) {
b = 0;
}
let g = (num & 0x0000FF) + amount;
if (g > 255) {
g = 255;
} else if (g < 0) {
g = 0;
}
let color = (g | (b << 8) | (r << 16)).toString(16);
while (color.length < 6){
color = 0 + color;
}
return (usePound ? '#' : '') + color;
}
I've rewritten this answer https://stackoverflow.com/a/13542669/4537906 into a pair of readable functions in TypeScript.
The reason is that in modern JavaScript we don't need to care about saving characters any more. That is done by the compilers. IMO we should aim for a readable and understandable code.
This is my approach:
tint.ts
type ColorObject = Record<"r" | "g" | "b" | "a", number>;
const singleColorSpace = 16 * 16; // 256
const blueSpace = singleColorSpace;
const greenSpace = blueSpace * singleColorSpace; // 65536
const redSpace = greenSpace * singleColorSpace; // 16777216
/* eslint-disable regex/invalid */
// adapted to TS from https://github.com/PimpTrizkit/PJs/wiki/12.-Shade,-Blend-and-Convert-a-Web-Color-(pSBC.js)
export const toColorObject = (rgbOrHex: string): ColorObject => {
const { length } = rgbOrHex;
const outputColor = {} as ColorObject;
if (length > 9) {
const rgbaColor = rgbOrHex.split(",");
const [rgbaAndRed, green, blue, alpha] = rgbaColor;
if (rgbaAndRed.slice(0, 3) !== "rgb") {
throw new Error("Invalid color format");
}
const red = rgbaAndRed[3] === "a" ? rgbaAndRed.slice(5) : rgbaAndRed.slice(4);
const rgbaLength = rgbaColor.length;
if (rgbaLength < 3 || rgbaLength > 4) {
return null;
}
outputColor.r = parseInt(red, 10);
outputColor.g = parseInt(green, 10);
outputColor.b = parseInt(blue, 10);
outputColor.a = alpha ? parseFloat(alpha) : -1;
} else {
if (length === 8 || length === 6 || length < 4) {
throw new Error("Invalid hex color format");
}
let HexColor = rgbOrHex;
if (length < 6) {
HexColor = `#${rgbOrHex[1]}${rgbOrHex[1]}${rgbOrHex[2]}${rgbOrHex[2]}${rgbOrHex[3]}${rgbOrHex[3]}${
length > 4 ? rgbOrHex[4] + rgbOrHex[4] : ""
}`;
}
if (length === 9 || length === 5) {
const hexRed = parseInt(HexColor.slice(1, 3), 16);
outputColor.r = hexRed;
const hexGreen = parseInt(HexColor.slice(3, 5), 16);
outputColor.g = hexGreen;
const hexBlue = parseInt(HexColor.slice(5, 7), 16);
outputColor.b = hexBlue;
const hexAlpha = parseInt(HexColor.slice(7, 9), 16);
outputColor.a = Math.round((hexAlpha / 255) * 100) / 100;
} else {
const hexRed = parseInt(HexColor.slice(1, 3), 16);
outputColor.r = hexRed;
const hexGreen = parseInt(HexColor.slice(3, 5), 16);
outputColor.g = hexGreen;
const hexBlue = parseInt(HexColor.slice(5, 7), 16);
outputColor.b = hexBlue;
outputColor.a = -1;
}
}
return outputColor;
};
const black: ColorObject = { r: 0, g: 0, b: 0, a: -1 };
const white: ColorObject = { r: 255, g: 255, b: 255, a: -1 };
export const tint = (
ratio: number,
inputColor: string,
{ toColor, useLinear, reformat }: { toColor?: string; useLinear?: boolean; reformat?: boolean } = {}
) => {
const { round } = Math;
const clampedRatio = Math.min(Math.max(ratio, -1), 1);
if (ratio < -1 || ratio > 1) {
// eslint-disable-next-line no-console
console.info(`Ratio should be between -1 and 1 and it is ${ratio}. It will be clamped to ${clampedRatio}`);
}
let baseColor = inputColor;
if (inputColor[0] !== "r" && inputColor[0] !== "#") {
baseColor = "#000";
// eslint-disable-next-line no-console
console.info(
`Invalid input color format. "${inputColor}" should be rgb(a) or hex. It will fallback to "${baseColor}"`
);
}
let isRGBformat = baseColor.length > 9 || baseColor.includes("rgb(");
isRGBformat = reformat ? !isRGBformat : isRGBformat;
if (toColor) {
const isToColorRgbFormat = (toColor && toColor?.length > 9) || toColor?.includes("rgb(");
isRGBformat = reformat ? !isToColorRgbFormat : isToColorRgbFormat;
}
const formattedBaseColor = toColorObject(baseColor);
const isNegativeRatio = clampedRatio < 0;
const toColorDefault = isNegativeRatio ? black : white;
const formattedToColor = toColor && !reformat ? toColorObject(toColor) : toColorDefault;
const toColorRatio = Math.abs(clampedRatio);
const baseRatio = 1 - toColorRatio;
const outputColor = {} as ColorObject;
if (useLinear) {
outputColor.r = round(baseRatio * formattedBaseColor.r + toColorRatio * formattedToColor.r);
outputColor.g = round(baseRatio * formattedBaseColor.g + toColorRatio * formattedToColor.g);
outputColor.b = round(baseRatio * formattedBaseColor.b + toColorRatio * formattedToColor.b);
} else {
outputColor.r = round((baseRatio * formattedBaseColor.r ** 2 + toColorRatio * formattedToColor.r ** 2) ** 0.5);
outputColor.g = round((baseRatio * formattedBaseColor.g ** 2 + toColorRatio * formattedToColor.g ** 2) ** 0.5);
outputColor.b = round((baseRatio * formattedBaseColor.b ** 2 + toColorRatio * formattedToColor.b ** 2) ** 0.5);
}
const blendedAlpha = formattedBaseColor.a * baseRatio + formattedToColor.a * toColorRatio;
outputColor.a = formattedToColor.a < 0 ? formattedBaseColor.a : blendedAlpha;
const hasAlpha = formattedBaseColor.a >= 0 || formattedToColor.a >= 0;
if (isRGBformat) {
return `rgb${hasAlpha ? "a" : ""}(${outputColor.r},${outputColor.g},${outputColor.b}${
hasAlpha ? `,${round(outputColor.a * 1000) / 1000}` : ""
})`;
}
return `#${(
outputColor.r * redSpace +
outputColor.g * greenSpace +
outputColor.b * blueSpace +
(hasAlpha ? round(outputColor.a * 255) : 0)
)
.toString(16)
// If no Alpha, we remove the last 2 hex digits
.slice(0, hasAlpha ? undefined : -2)}`;
};
And also a collection of jest tests
tint.test.ts
import { tint, toColorObject } from "./tint";
const rgbBlue = "rgb(20,60,200)";
const rgbaBlue = "rgba(20,60,200,0.67423)";
const hex6Cyan = "#67DAF0";
const hex3Pink = "#F3A";
const hex4Pink = "#F3A9";
const rbgBrown = "rgb(200,60,20)";
const rgbaBrown = "rgba(200,60,20,0.98631)";
describe("tint", () => {
describe("Logarithmic blending", () => {
describe("Shades", () => {
it("lightens rgb color", () => {
expect(tint(0.42, rgbBlue)).toEqual("rgb(166,171,225)");
});
it("darkens hex color", () => {
expect(tint(-0.4, hex3Pink)).toEqual("#c62884");
});
it("lightens rgba color", () => {
expect(tint(0.42, rgbaBrown)).toEqual("rgba(225,171,166,0.986)");
});
it("returns black with ratio -1", () => {
expect(tint(-1, rgbBlue)).toEqual("rgb(0,0,0)");
});
});
describe("converts color notation", () => {
it("converts from rgba to hexa", () => {
// expect(tint(0.42, color2, "c")).toEqual("#a6abe1ac");
expect(tint(0.42, rgbaBlue, { reformat: true })).toEqual("#a6abe1ac");
});
it("converts from hexa to rgba", () => {
// expect(tint(0, color6, "c", true)).toEqual("rgba(255,51,170,0.6)");
expect(tint(0, hex4Pink, { reformat: true })).toEqual("rgba(255,51,170,0.6)");
});
it("converts and returns white with ratio 1", () => {
expect(tint(1, hex3Pink, { reformat: true })).toEqual("rgb(255,255,255)");
});
});
describe("Blends two colors", () => {
it("blends rgba with rgba", () => {
expect(tint(-0.5, rgbaBlue, { toColor: rgbaBrown })).toEqual("rgba(142,60,142,0.83)");
});
it("blends rgba with rgb", () => {
expect(tint(0.7, rgbaBlue, { toColor: rbgBrown })).toEqual("rgba(168,60,111,0.674)");
});
it("blends hex with rgb", () => {
expect(tint(0.25, hex6Cyan, { toColor: rbgBrown })).toEqual("rgb(134,191,208)");
});
it("blends rgb with hex", () => {
expect(tint(0.75, rbgBrown, { toColor: hex6Cyan })).toEqual("#86bfd0");
});
});
});
describe("Linear Blending", () => {
describe("Shades", () => {
it("lightens rgb color", () => {
expect(tint(0.42, rgbBlue, { useLinear: true })).toEqual("rgb(119,142,223)");
});
it("darkens hex color", () => {
expect(tint(-0.4, hex3Pink, { useLinear: true })).toEqual("#991f66");
});
it("lightens rgba color", () => {
expect(tint(0.42, rgbaBrown, { useLinear: true })).toEqual("rgba(223,142,119,0.986)");
});
it("returns black with ratio -1", () => {
expect(tint(-1, rgbBlue, { useLinear: true })).toEqual("rgb(0,0,0)");
});
});
describe("converts color notation", () => {
it("converts from rgba to hexa", () => {
expect(tint(0.42, rgbaBlue, { reformat: true, useLinear: true })).toEqual("#778edfac");
});
it("converts from hexa to rgba", () => {
expect(tint(0, hex4Pink, { reformat: true, useLinear: true })).toEqual("rgba(255,51,170,0.6)");
});
it("converts and returns white with ratio 1", () => {
expect(tint(1, hex3Pink, { useLinear: true, reformat: true })).toEqual("rgb(255,255,255)");
});
});
describe("Blends two colors", () => {
it("blends rgba with rgba", () => {
expect(tint(-0.5, rgbaBlue, { toColor: rgbaBrown, useLinear: true })).toEqual("rgba(110,60,110,0.83)");
});
it("blends rgba with rgb", () => {
expect(tint(0.7, rgbaBlue, { toColor: rbgBrown, useLinear: true })).toEqual("rgba(146,60,74,0.674)");
});
it("blends hex with rgb", () => {
expect(tint(0.25, hex6Cyan, { toColor: rbgBrown, useLinear: true })).toEqual("rgb(127,179,185)");
});
it("blends rgb with hex", () => {
expect(tint(0.75, rbgBrown, { toColor: hex6Cyan, useLinear: true })).toEqual("#7fb3b9");
});
});
});
describe("Error handling", () => {
describe("When invalid hex color provided", () => {
it.each([1, 2, 5])("throws error if hex color has %s characters", (n) => {
const correlativeNumbers = Array.from(Array(n).keys()).join("");
expect(() => tint(0, `#${correlativeNumbers}`)).toThrow("Invalid hex color format");
});
});
describe("When ratio is not between -1 and 1", () => {
it("clamps ratio to -1", () => {
expect(tint(-43, rgbBlue)).toEqual("rgb(0,0,0)");
});
it("clamps ratio to 1", () => {
expect(tint(42, rgbBlue)).toEqual("rgb(255,255,255)");
});
});
});
});
describe("toColorObject function", () => {
it("should return a color object from hex", () => {
expect(toColorObject("#fff")).toEqual({
r: 255,
g: 255,
b: 255,
a: -1,
});
});
it("should return a color object from hex with alpha", () => {
expect(toColorObject("#fff6")).toEqual({
r: 255,
g: 255,
b: 255,
a: 0.4,
});
});
it("should return a color object from rgb", () => {
expect(toColorObject("rgb(255,255,255)")).toEqual({
r: 255,
g: 255,
b: 255,
a: -1,
});
});
it("should return a color object from rgba", () => {
expect(toColorObject("rgba(255,255,255,1)")).toEqual({
r: 255,
g: 255,
b: 255,
a: 1,
});
});
describe("Error handling", () => {
it("should throw error if invalid color provided", () => {
expect(() => toColorObject("foo")).toThrow("Invalid hex color format");
});
it("should throw error if invalid color provided", () => {
expect(() => toColorObject("invalid color")).toThrow("Invalid color format");
});
});
});
I hope you enjoy it. It's very simple but it works pretty fine
I made a port of the excellent xcolor library to remove its jQuery dependency. There are a ton of functions in there including lightening and darkening colors.
Really, converting hex to RGB is a completely separate function from lightening or darkening colors. Keep things DRY please. In any case, once you have an RGB color, you can just add the difference between the light level you want and the light level you have to each of the RGB values:
var lightness = function(level) {
if(level === undefined) {
return Math.max(this.g,this.r,this.b)
} else {
var roundedLevel = Math.round(level) // fractions won't work here
var levelChange = roundedLevel - this.lightness()
var r = Math.max(0,this.r+levelChange)
var g = Math.max(0,this.g+levelChange)
var b = Math.max(0,this.b+levelChange)
if(r > 0xff) r = 0xff
if(g > 0xff) g = 0xff
if(b > 0xff) b = 0xff
return xolor({r: r, g: g, b: b})
}
}
var lighter = function(amount) {
return this.lightness(this.lightness()+amount)
}
See https://github.com/fresheneesz/xolor for more of the source.
I've long wanted to be able to produce tints/shades of colours, here is my JavaScript solution:
const varyHue = function (hueIn, pcIn) {
const truncate = function (valIn) {
if (valIn > 255) {
valIn = 255;
} else if (valIn < 0) {
valIn = 0;
}
return valIn;
};
let red = parseInt(hueIn.substring(0, 2), 16);
let green = parseInt(hueIn.substring(2, 4), 16);
let blue = parseInt(hueIn.substring(4, 6), 16);
let pc = parseInt(pcIn, 10); //shade positive, tint negative
let max = 0;
let dif = 0;
max = red;
if (pc < 0) { //tint: make lighter
if (green < max) {
max = green;
}
if (blue < max) {
max = blue;
}
dif = parseInt(((Math.abs(pc) / 100) * (255 - max)), 10);
return leftPad(((truncate(red + dif)).toString(16)), '0', 2) + leftPad(((truncate(green + dif)).toString(16)), '0', 2) + leftPad(((truncate(blue + dif)).toString(16)), '0', 2);
} else { //shade: make darker
if (green > max) {
max = green;
}
if (blue > max) {
max = blue;
}
dif = parseInt(((pc / 100) * max), 10);
return leftPad(((truncate(red - dif)).toString(16)), '0', 2) + leftPad(((truncate(green - dif)).toString(16)), '0', 2) + leftPad(((truncate(blue - dif)).toString(16)), '0', 2);
}
};
thank all for all good answer and edits, they help me to I develop this helpful solution, as I think.
/**
* #param color Hex value format: #ffffff or ffffff or fff or ffffff00 with opacity or rgb rgba formats
* #param decimal lighten or darken decimal value, example -0.5 to darken by 50% or 0.5 to lighten by 50%.
*/
function shadeColor(color, decimal){
if(typeof color!=="string" || !color)return color;
if(typeof decimal !== "number" || !decimal)return color;
var r,g,b;
var _format="";
color=color.trim();
if(/^rgba?\((\s*[0-9]{1,3}\s*)(,\s*[0-9]{1,3}\s*){2}(,\s*[0-9](\.[0-9]+)?\s*)?\)$/i.test(color)){
color=color.replace(/[a-z)(\s]+/ig,'').split(/,/)
.map(function (c,i) { return ((i===3)?c:parseInt(c)); });
_format="rgb";
if(color.length===4) {
_format += "a(VAL," + color[3]+")";
color.pop();
}else _format+="(VAL)";
}else if(/^#?([a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})$/i.test(color)){
var group=color.length>5?2:1;
color=color.replace("#","")
.split(new RegExp("([a-f0-9]{"+group.toString()+"})",'i'))
.filter(function(c){return c.length===group;})
.map(function (c,i) { if(group===1)c+=c;return ((i===3)?c:parseInt(c,16)); });
_format="#VAL";
if(color.length===4) {
_format += color[3];
color.pop();
}
}else return color;
if(decimal<=1 && decimal>=-1)decimal*=100;
color=color.reduce(function (_c,c) {
c=Math.round(c * (100 + decimal) / 100);
c= (c < 255)? c : 255;
if(/^#/.test(_format)){
c=c.toString(16);
if(c.length === 1)c="0"+c;
return _c+c;
}
if(_c==="")return c;
return _c+","+c;
},"");
return _format.replace("VAL",color);
}
You Can Use to dark or light, example:
//lighten
shadeColor("#abcdef",0.5);
shadeColor("#abcdef",0.3);
shadeColor("#abcdef",50);
shadeColor("#abcdef",30);
//it's work with all color format supported in css3 and transparent future
//for example transparnet
shadeColor("#abc8",0.5);
shadeColor("#abcdefff",0.5);
shadeColor("#abcf",35);
//for example rgb and rgba
shadeColor("rgb(128,33,244)",0.5);
shadeColor("rgba(128,33,244,0.3)",0.5);
//darken like above except the decimal with negative number (less than 0)
shadeColor("#abcdef",-0.5);
shadeColor("#abcdef",-50);
shadeColor("rgb(128,33,244)",-35);
How to simple shade color in PHP?
<?php
function shadeColor ($color='#cccccc', $percent=-25) {
$color = Str_Replace("#",Null,$color);
$r = Hexdec(Substr($color,0,2));
$g = Hexdec(Substr($color,2,2));
$b = Hexdec(Substr($color,4,2));
$r = (Int)($r*(100+$percent)/100);
$g = (Int)($g*(100+$percent)/100);
$b = (Int)($b*(100+$percent)/100);
$r = Trim(Dechex(($r<255)?$r:255));
$g = Trim(Dechex(($g<255)?$g:255));
$b = Trim(Dechex(($b<255)?$b:255));
$r = ((Strlen($r)==1)?"0{$r}":$r);
$g = ((Strlen($g)==1)?"0{$g}":$g);
$b = ((Strlen($b)==1)?"0{$b}":$b);
return (String)("#{$r}{$g}{$b}");
}
echo shadeColor(); // #999999

How can I generate the opposite color according to current color?

I'm trying to create a color opposite of current color. I mean if current color is black, then I need to generate white.
Actually I have a text (the color of this text is dynamic, its color can be made at random). That text is into a div and I need to set the opposite color of that text for the background-color of div. I would like that text be clear in the div (color perspective).
The opposite color means: Dark / Bright
I have the current color of text and I can pass it to this function:
var TextColor = #F0F0F0; // for example (it is a bright color)
function create_opp_color(current color) {
// create opposite color according to current color
}
create_opp_color(TextColor); // this should be something like "#202020" (or a dark color)
Is there any idea to create create_opp_color() function?
UPDATE: Production-ready code on GitHub.
This is how I'd do it:
Convert HEX to RGB
Invert the R,G and B components
Convert each component back to HEX
Pad each component with zeros and output.
function invertColor(hex) {
if (hex.indexOf('#') === 0) {
hex = hex.slice(1);
}
// convert 3-digit hex to 6-digits.
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
if (hex.length !== 6) {
throw new Error('Invalid HEX color.');
}
// invert color components
var r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16),
g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16),
b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16);
// pad each with zeros and return
return '#' + padZero(r) + padZero(g) + padZero(b);
}
function padZero(str, len) {
len = len || 2;
var zeros = new Array(len).join('0');
return (zeros + str).slice(-len);
}
Example Output:
Advanced Version:
This has a bw option that will decide whether to invert to black or white; so you'll get more contrast which is generally better for the human eye.
function invertColor(hex, bw) {
if (hex.indexOf('#') === 0) {
hex = hex.slice(1);
}
// convert 3-digit hex to 6-digits.
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
if (hex.length !== 6) {
throw new Error('Invalid HEX color.');
}
var r = parseInt(hex.slice(0, 2), 16),
g = parseInt(hex.slice(2, 4), 16),
b = parseInt(hex.slice(4, 6), 16);
if (bw) {
// https://stackoverflow.com/a/3943023/112731
return (r * 0.299 + g * 0.587 + b * 0.114) > 186
? '#000000'
: '#FFFFFF';
}
// invert color components
r = (255 - r).toString(16);
g = (255 - g).toString(16);
b = (255 - b).toString(16);
// pad each with zeros and return
return "#" + padZero(r) + padZero(g) + padZero(b);
}
Example Output:
Simple and elegant.
function invertHex(hex) {
return (Number(`0x1${hex}`) ^ 0xFFFFFF).toString(16).substr(1).toUpperCase()
}
invertHex('00FF00'); // Returns FF00FF
Simple way to achieve this with CSS:
mix-blend-mode: difference;
color:white;
Pure CSS implementation of #Onur's answer bw part.
<input type="color" oninput="['--r','--g','--b'].forEach((k,i)=>this.nextElementSibling.style.setProperty(k,parseInt(event.target.value.slice(1+i*2,3+i*2),16)))" />
<div style="--r: 0; --g: 0; --b: 0; --c: calc(-1 * ((var(--r) * 0.299 + var(--g) * 0.587 + var(--b) * 0.114) - 186) * 255)">
<div style="background-color: rgb(var(--r), var(--g), var(--b)); color: rgb(var(--c), var(--c), var(--c))">Test</div>
</div>
How about, CSS filter: invert(1), it has a decent cross-browser compatibility and it work with text and images or whatever your content is.
For a black and white inverted color add some more filters filter: saturate(0) grayscale(1) brightness(.7) contrast(1000%) invert(1)
const div = document.querySelector("div");
const b = document.querySelector("b");
const input = document.querySelector("input");
input.oninput = (e) => {
const color = e.target.value;
div.style.background = color;
b.style.color = color;
b.innerText = color;
}
body {
font-family: Arial;
background: #333;
}
div {
position: relative;
display: inline-flex;
justify-content: center;
align-items: center;
min-width: 100px;
padding: .5em 1em;
border: 2px solid #FFF;
border-radius: 15px;
background: #378ad3;
font-style: normal;
}
b {
/* Inverting the color */
/* ᐯᐯᐯᐯᐯᐯᐯᐯᐯᐯᐯᐯ */
filter: saturate(0) grayscale(1) brightness(.7) contrast(1000%) invert(1);
}
input {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
<div>
<b>#378ad3</b>
<input type="color" value="#378ad3"/>
</div>
Watch out Accesibility (AA/AAA). Colour contrast by itself is useless. Really different colors can have no contrast at all for colour blind people.
IMHO a calculation for such a color could go like this:
(Use "HLS" for simplicity)
Rotate Hue 180º to get the (maybe useless) maximal color contrast
Calculate Brightness Difference.
( Calculate Colour Difference... unnecesary, it's maximal or almost )
Calculate Contrast Ratio.
If the resulting color complies the requirements calculation ends, if not, loop:
If Brightness Difference is not enought increase or
decrese calculated color luminosity (L) by a certain amount or ratio (up or
down depending on the original colour brightness: > or < than the mid
value)
Check if it complies your requirements, if it does calculation ends.
if luminosity can be increased (or decrased) any more there is no valid color to comply the requirements, just try black and white, take "the best one" of those (probably the one with bigger contrast ratio) and end.
In my understanding of your question, by opposite color you mean inverted color.
InvertedColorComponent = 0xFF - ColorComponent
So for the color red (#FF0000) this means:
R = 0xFF or 255
G = 0x00 or 0
B = 0x00 or 0
inverted color red (#00FFFF) is:
R = 0xFF - 0xFF = 0x00 or 255 - 255 = 0
G = 0xFF - 0x00 = 0xFF or 255 - 0 = 255
B = 0xFF - 0x00 = 0xFF or 255 - 0 = 255
Another examples:
Black (#000000) becomes White (#FFFFFF).
Orange (#FFA500) becomes #005AFF
This is a simple function that invert an hexadecimal color
const invertColor = (col) => {
col = col.toLowerCase();
const colors = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']
let inverseColor = '#'
col.replace('#','').split('').forEach(i => {
const index = colors.indexOf(i)
inverseColor += colors.reverse()[index]
})
return inverseColor
}
Codepen example
Simply flipping background color to text color won't work with some middle range values, e.g. 0x808080. I had tried with shifting the color values instead - (v + 0x80) % 0x100. See a demo here.
Agreeing with the comment from miguel-svq - although expecting to see more detailed algorithms for each calculation step.
It is possible to convert a HEX color using the snippets
function invertColor(color) {
return '#' + ("000000" + (0xFFFFFF ^ parseInt(color.substring(1),16)).toString(16)).slice(-6);
}
Function to Invert Color of Element. Gets the luminosity of each and if they are close, inverts text color.
function adjustColor(element) {
var style = window.getComputedStyle(element);
var background = new Color(style['background-color']);
var text = new Color(style['color']);
if (Math.abs(background.luma - text.luma) < 100) {
element.style.color = text.inverted.toString();
}
}
The Color "Class" below. Accepts hex, rgb, rgba (even with percents), and can output to either one as well. Explorer will need polyfills for String.padStart and String.startsWith and the interpolated string in the toString() method will need to be modified using concat instead.
const Color = (function () {
function toHex(num, padding) { return num.toString(16).padStart(padding || 2); }
function parsePart(value) {
var perc = value.lastIndexOf('%');
return perc < 0 ? value : value.substr(0, perc);
}
function Color(data) {
if (arguments.length > 1) {
this[0] = arguments[0];
this[1] = arguments[1];
this[2] = arguments[2];
if (arguments.length > 3) { this[3] = arguments[3]; }
} else if (data instanceof Color || Array.isArray(data)) {
this[0] = data[0];
this[1] = data[1];
this[2] = data[2];
this[3] = data[3];
} else if (typeof data === 'string') {
data = data.trim();
if (data[0] === "#") {
switch (data.length) {
case 4:
this[0] = parseInt(data[1], 16); this[0] = (this[0] << 4) | this[0];
this[1] = parseInt(data[2], 16); this[1] = (this[1] << 4) | this[1];
this[2] = parseInt(data[3], 16); this[2] = (this[2] << 4) | this[2];
break;
case 9:
this[3] = parseInt(data.substr(7, 2), 16);
//Fall Through
case 7:
this[0] = parseInt(data.substr(1, 2), 16);
this[1] = parseInt(data.substr(3, 2), 16);
this[2] = parseInt(data.substr(5, 2), 16);
break;
}
} else if (data.startsWith("rgb")) {
var parts = data.substr(data[3] === "a" ? 5 : 4, data.length - (data[3] === "a" ? 6 : 5)).split(',');
this.r = parsePart(parts[0]);
this.g = parsePart(parts[1]);
this.b = parsePart(parts[2]);
if (parts.length > 3) { this.a = parsePart(parts[3]); }
}
}
}
Color.prototype = {
constructor: Color,
0: 255,
1: 255,
2: 255,
3: 255,
get r() { return this[0]; },
set r(value) { this[0] = value == null ? 0 : Math.max(Math.min(parseInt(value), 255), 0); },
get g() { return this[1]; },
set g(value) { this[1] = value == null ? 0 : Math.max(Math.min(parseInt(value), 255), 0); },
get b() { return this[2]; },
set b(value) { this[2] = value == null ? 0 : Math.max(Math.min(parseInt(value), 255), 0); },
get a() { return this[3] / 255; },
set a(value) { this[3] = value == null ? 255 : Math.max(Math.min(value > 1 ? value : parseFloat(value) * 255, 255), 0); },
get luma() { return .299 * this.r + .587 * this.g + .114 * this.b; },
get inverted() { return new Color(255 - this[0], 255 - this[1], 255 - this[2], this[3]); },
toString: function (option) {
if (option === 16) {
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (this[3] === 255 ? '' : toHex(this[3]));
} else if (option === '%') {
if (this.a !== 1) {
return `rgba(${this.r / 255 * 100}%, ${this.b / 255 * 100}%, ${this.g / 255 * 100}%, ${this.a / 255})`;
} else {
return `rgb(${this.r / 255 * 100}%, ${this.b / 255 * 100}%, ${this.g / 255 * 100})%`;
}
} else {
if (this.a !== 1) {
return `rgba(${this.r}, ${this.b}, ${this.g}, ${this.a})`;
} else {
return `rgb(${this.r}, ${this.b}, ${this.g})`;
}
}
}
};
return Color;
}());
Python alternatives of Onur's answer:
def hex_to_rgb(value):
value = value.lstrip('#')
lv = len(value)
return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
def invertColor(color, bw=False):
# strip the # from the beginning
color = color.lstrip('#')
# convert the string into hex
color = int(color, 16)
# invert the three bytes
# as good as substracting each of RGB component by 255(FF)
comp_color = 0xFFFFFF ^ color
# convert the color back to hex by prefixing a #
comp_color = "#%06X" % comp_color
rgb_color = hex_to_rgb(comp_color)
if (bw):
# http://stackoverflow.com/a/3943023/112731
bw_value = rgb_color[0]*0.299 + rgb_color[0]*0.587 + rgb_color[0]*0.114
if (bw_value>186):
comp_color = "#FFFFFF"
else:
comp_color = "#000000"
# return the result
return comp_color
color = "#fffff1"
print invertColor(color, bw=True)
For Typescript lovers, here what I use:
invertHex(hex: string) {
if (hex.indexOf('#') === 0) {
hex = hex.slice(1);
}
if (hex.length != 6) {
console.warn('Hex color must be six hex numbers in length.');
return '#' + hex;
}
hex = hex.toUpperCase();
const splitNum = hex.split('');
let resultNum = '';
const simpleNum = 'FEDCBA9876'.split('');
const complexNum = {
A: '5', B: '4', C: '3', D: '2', E: '1', F: '0'
};
for (let i = 0; i < 6; i++) {
if (!isNaN(Number(splitNum[i]))) {
resultNum += simpleNum[splitNum[i]];
} else if (complexNum[splitNum[i]]) {
resultNum += complexNum[splitNum[i]];
} else {
console.warn('Hex colors must only include hex numbers 0-9, and A-F');
return '#' + hex;
}
}
return '#' + resultNum;
}

JavaScript - how do you programmatically calculate colours?

In a JavaScript/jQuery app, I want to set the color of a div element based on an external variable. From low to high values of this variable, you go past the colors green to red. Now, I could do this:
setColor: function(quantity)
{
var color;
if(quantity <= -1000)
{
color = '#00ff00'
}
else if(quantity > -1000 && quantity <= -900)
{
color = '#11ee00'
}
// a million more else if statements
return color;
}
Anything that's -1000 or below is #00ff00 (green), and everything that's +1000 or above is #ff0000 (red), with 0 being #ffff00 (yellow). There's lots of color variations in between these 3 extremes: for example, a value of -950 would be a slightly more redder shade of green than -951.
But isn't there a formula for this kind of stuff, so that I don't end with a 1000 line function?
"A slightly more redder shade of green" becomes yellowish, because the color in the middle between red and green is yellow. So this function returns an RGB-string beeing pure green when value is <= lower limit, red when value >= upper limit and yellow when value is middle (0 in your case), and all shades in between.
var low = -1000, upp = 1000,
mid = (upp + low) / 2, dif = (upp - low) / 2;
function grade(value) {
var r = 255, g = 255, b = 0;
if (value <= low) r = 0;
else if (value >= upp) g = 0;
else if (value < mid) r = Math.round(255 * Math.abs(low - value) / dif);
else if (value > mid) g = Math.round(255 * Math.abs(upp - value) / dif);
return 'rgb(' + r + ',' + g + ',' + b + ')';
}
Adjust vars low and upp to your usecase. The function is easy to adapt to colorchanges between green/cyan/blue or red/purple/blue. If you need a full rainbow some more effort is required.
First of all it is good to separate decoration logic(color value) and set classes for the different colors and set those to the div.
I had a task of that kind and used sin fuctions of every chanel with different amplitude, period and shift for every channel:
var ColorPicker = {
colors: {},
init: function(range){
var ra = 190,
rp = 8/3*range,
rs = range*2/3;
var ga = 190,
gp = range,
gs = 0;
var ba = 150,
bp = 8/3*range,
bs = range;
var pipi = 2*Math.PI;
var r, g, b, w;
for (var i = 0; i < range; i++) {
r = ra*Math.cos(i*pipi/rp + rs*pipi/range);
g = ga*Math.cos(i*pipi/gp + gs*pipi/range);
b = ba*Math.cos(i*pipi/bp + bs*pipi/range);
r = Math.round( ( r > 0 ) ? r : 0 );
g = Math.round( ( g > 0 ) ? g : 0 );
b = Math.round( ( b > 0 ) ? b : 0 );
w = Math.round( i/range*255);
this.colors[i] = {red: r, green: g, blue: b, white: w};
};
},
getColorObj: function(grade){
return this.colors[grade];
},
getRGB: function(grade, coef){
var o = this.colors[grade];
if (!coef){
coef = 1;
}
var r = (Math.round(o.red*coef)>255)?255:Math.round(o.red*coef),
g = (Math.round(o.green*coef)>255)?255:Math.round(o.green*coef),
b = (Math.round(o.blue*coef)>255)?255:Math.round(o.blue*coef);
return 'rgb(' + r + ','
+ g + ','
+ b + ')';
},
// get shades of BW
getW: function(grade){
var o = this.colors[grade];
return 'rgb(' + o.white + ','
+ o.white + ','
+ 0.9*o.white + ')';
},
// opposite to BW
getB: function(grade){
var o = this.colors[grade],
b = 255 - o.white;
// console.log(b);
return 'rgb(' + b + ','
+ b + ','
+ .9*b + ')';
},
};
May need to rewrite it though. Don't think it is an optimal solution now.
There is a color manipulation library that's called TinyColor.
What you want to do, is vary the hue. This would be done like this:
var red = tinycolor("#FF0000").toHsv();
var green = tinycolor("#00FF00").toHsv(), h, col, hex;
h = green.h + (red.h-green.h)*(quantity+1000)/2000;
col = tinycolor({h: h, s: 0.5, v: 0.5});
hex = col.toHex();
See demo here

Programmatically Lighten or Darken a hex color (or rgb, and blend colors)

Here is a function I was working on to programmatically lighten or darken a hex color by a specific amount. Just pass in a string like "3F6D2A" for the color (col) and a base10 integer (amt) for the amount to lighten or darken. To darken, pass in a negative number (i.e. -20).
The reason for me to do this was because of all the solutions I found, thus far, they seemed to over-complicate the issue. And I had a feeling it could be done with just a couple lines of code. Please let me know if you find any problems, or have any adjustments to make that would speed it up.
function LightenDarkenColor(col, amt) {
col = parseInt(col, 16);
return (((col & 0x0000FF) + amt) | ((((col >> 8) & 0x00FF) + amt) << 8) | (((col >> 16) + amt) << 16)).toString(16);
}
// TEST
console.log( LightenDarkenColor("3F6D2A",40) );
For Development use here is an easier to read version:
function LightenDarkenColor(col, amt) {
var num = parseInt(col, 16);
var r = (num >> 16) + amt;
var b = ((num >> 8) & 0x00FF) + amt;
var g = (num & 0x0000FF) + amt;
var newColor = g | (b << 8) | (r << 16);
return newColor.toString(16);
}
// TEST
console.log(LightenDarkenColor("3F6D2A", -40));
And finally a version to handle colors that may (or may not) have the "#" in the beginning. Plus adjusting for improper color values:
function LightenDarkenColor(col,amt) {
var usePound = false;
if ( col[0] == "#" ) {
col = col.slice(1);
usePound = true;
}
var num = parseInt(col,16);
var r = (num >> 16) + amt;
if ( r > 255 ) r = 255;
else if (r < 0) r = 0;
var b = ((num >> 8) & 0x00FF) + amt;
if ( b > 255 ) b = 255;
else if (b < 0) b = 0;
var g = (num & 0x0000FF) + amt;
if ( g > 255 ) g = 255;
else if ( g < 0 ) g = 0;
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
}
OK, so now it's not just a couple of lines, but it seems far simpler and if you're not using the "#" and don't need to check for colors out of range, it is only a couple of lines.
If not using the "#", you can just add it in code like:
var myColor = "3F6D2A";
myColor = LightenDarkenColor(myColor,10);
thePlaceTheColorIsUsed = ("#" + myColor);
I guess my main question is, am I correct here? Does this not encompass most (normal) situations? And if so, what is the fastest and smallest way to do this? I want to use in animations and in a small environment, so speed is the first most important factor here, size second, accuracy third, readability? huh? not on the list of requirements (sorry, I know half of you are tearing out your eyes right now!).
Well, this answer has become its own beast. Many new versions, it was getting stupid long. Many thanks to all of the great many contributors to this answer. But, in order to keep it simple for the masses. I archived all the versions/history of this answer's evolution to my github. And started it over clean on StackOverflow here with the newest version. A special thanks goes out to Mike 'Pomax' Kamermans for this version. He gave me the new math.
This function (pSBC) will take a HEX or RGB web color. pSBC can shade it darker or lighter, or blend it with a second color, and can also pass it right thru but convert from Hex to RGB (Hex2RGB) or RGB to Hex (RGB2Hex). All without you even knowing what color format you are using.
This runs really fast, probably the fastest, especially considering its many features. It was a long time in the making. See the whole story on my github. If you want the absolutely smallest and fastest possible way to shade or blend, see the Micro Functions below and use one of the 2-liner speed demons. They are great for intense animations, but this version here is fast enough for most animations.
This function uses Log Blending or Linear Blending. However, it does NOT convert to HSL to properly lighten or darken a color. Therefore, results from this function will differ from those much larger and much slower functions that use HSL.
jsFiddle with pSBC
github > pSBC Wiki
Features:
Auto-detects and accepts standard Hex colors in the form of strings. For example: "#AA6622" or "#bb551144".
Auto-detects and accepts standard RGB colors in the form of strings. For example: "rgb(123,45,76)" or "rgba(45,15,74,0.45)".
Shades colors to white or black by percentage.
Blends colors together by percentage.
Does Hex2RGB and RGB2Hex conversion at the same time, or solo.
Accepts 3 digit (or 4 digit w/ alpha) HEX color codes, in the form #RGB (or #RGBA). It will expand them. For Example: "#C41" becomes "#CC4411".
Accepts and (Linear) blends alpha channels. If either the c0 (from) color or the c1 (to) color has an alpha channel, then the returned color will have an alpha channel. If both colors have an alpha channel, then the returned color will be a linear blend of the two alpha channels using the percentage given (just as if it were a normal color channel). If only one of the two colors has an alpha channel, this alpha will just be passed thru to the returned color. This allows one to blend/shade a transparent color while maintaining the transparency level. Or, if the transparency levels should blend as well, make sure both colors have alphas. When shading, it will pass the alpha channel straight thru. If you want basic shading that also shades the alpha channel, then use rgb(0,0,0,1) or rgb(255,255,255,1) as your c1 (to) color (or their hex equivalents). For RGB colors, the returned color's alpha channel will be rounded to 3 decimal places.
RGB2Hex and Hex2RGB conversions are implicit when using blending. Regardless of the c0 (from) color; the returned color will always be in the color format of the c1 (to) color, if one exists. If there is no c1 (to) color, then pass 'c' in as the c1 color and it will shade and convert whatever the c0 color is. If conversion only is desired, then pass 0 in as the percentage (p) as well. If the c1 color is omitted or a non-string is passed in, it will not convert.
A secondary function is added to the global as well. pSBCr can be passed a Hex or RGB color and it returns an object containing this color information. Its in the form: {r: XXX, g: XXX, b: XXX, a: X.XXX}. Where .r, .g, and .b have range 0 to 255. And when there is no alpha: .a is -1. Otherwise: .a has range 0.000 to 1.000.
For RGB output, it outputs rgba() over rgb() when a color with an alpha channel was passed into c0 (from) and/or c1 (to).
Minor Error Checking has been added. It's not perfect. It can still crash or create jibberish. But it will catch some stuff. Basically, if the structure is wrong in some ways or if the percentage is not a number or out of scope, it will return null. An example: pSBC(0.5,"salt") == null, where as it thinks #salt is a valid color. Delete the four lines which end with return null; to remove this feature and make it faster and smaller.
Uses Log Blending. Pass true in for l (the 4th parameter) to use Linear Blending.
Code:
// Version 4.0
const pSBC=(p,c0,c1,l)=>{
let r,g,b,P,f,t,h,i=parseInt,m=Math.round,a=typeof(c1)=="string";
if(typeof(p)!="number"||p<-1||p>1||typeof(c0)!="string"||(c0[0]!='r'&&c0[0]!='#')||(c1&&!a))return null;
if(!this.pSBCr)this.pSBCr=(d)=>{
let n=d.length,x={};
if(n>9){
[r,g,b,a]=d=d.split(","),n=d.length;
if(n<3||n>4)return null;
x.r=i(r[3]=="a"?r.slice(5):r.slice(4)),x.g=i(g),x.b=i(b),x.a=a?parseFloat(a):-1
}else{
if(n==8||n==6||n<4)return null;
if(n<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(n>4?d[4]+d[4]:"");
d=i(d.slice(1),16);
if(n==9||n==5)x.r=d>>24&255,x.g=d>>16&255,x.b=d>>8&255,x.a=m((d&255)/0.255)/1000;
else x.r=d>>16,x.g=d>>8&255,x.b=d&255,x.a=-1
}return x};
h=c0.length>9,h=a?c1.length>9?true:c1=="c"?!h:false:h,f=this.pSBCr(c0),P=p<0,t=c1&&c1!="c"?this.pSBCr(c1):P?{r:0,g:0,b:0,a:-1}:{r:255,g:255,b:255,a:-1},p=P?p*-1:p,P=1-p;
if(!f||!t)return null;
if(l)r=m(P*f.r+p*t.r),g=m(P*f.g+p*t.g),b=m(P*f.b+p*t.b);
else r=m((P*f.r**2+p*t.r**2)**0.5),g=m((P*f.g**2+p*t.g**2)**0.5),b=m((P*f.b**2+p*t.b**2)**0.5);
a=f.a,t=t.a,f=a>=0||t>=0,a=f?a<0?t:t<0?a:a*P+t*p:0;
if(h)return"rgb"+(f?"a(":"(")+r+","+g+","+b+(f?","+m(a*1000)/1000:"")+")";
else return"#"+(4294967296+r*16777216+g*65536+b*256+(f?m(a*255):0)).toString(16).slice(1,f?undefined:-2)
}
Usage:
// Setup:
let color1 = "rgb(20,60,200)";
let color2 = "rgba(20,60,200,0.67423)";
let color3 = "#67DAF0";
let color4 = "#5567DAF0";
let color5 = "#F3A";
let color6 = "#F3A9";
let color7 = "rgb(200,60,20)";
let color8 = "rgba(200,60,20,0.98631)";
// Tests:
/*** Log Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1 ); // rgb(20,60,200) + [42% Lighter] => rgb(166,171,225)
pSBC ( -0.4, color5 ); // #F3A + [40% Darker] => #c62884
pSBC ( 0.42, color8 ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(225,171,166,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c" ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #a6abe1ac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c" ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8 ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(142,60,142,0.83)
pSBC ( 0.7, color2, color7 ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(168,60,111,0.67423)
pSBC ( 0.25, color3, color7 ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(134,191,208)
pSBC ( 0.75, color7, color3 ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #86bfd0
/*** Linear Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1, false, true ); // rgb(20,60,200) + [42% Lighter] => rgb(119,142,223)
pSBC ( -0.4, color5, false, true ); // #F3A + [40% Darker] => #991f66
pSBC ( 0.42, color8, false, true ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(223,142,119,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c", true ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #778edfac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c", true ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8, true ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(110,60,110,0.83)
pSBC ( 0.7, color2, color7, true ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(146,60,74,0.67423)
pSBC ( 0.25, color3, color7, true ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(127,179,185)
pSBC ( 0.75, color7, color3, true ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #7fb3b9
/*** Other Stuff ***/
// Error Checking
pSBC ( 0.42, "#FFBAA" ); // #FFBAA + [42% Lighter] => null  (Invalid Input Color)
pSBC ( 42, color1, color5 ); // rgb(20,60,200) + #F3A + [4200% Blend] => null  (Invalid Percentage Range)
pSBC ( 0.42, {} ); // [object Object] + [42% Lighter] => null  (Strings Only for Color)
pSBC ( "42", color1 ); // rgb(20,60,200) + ["42"] => null  (Numbers Only for Percentage)
pSBC ( 0.42, "salt" ); // salt + [42% Lighter] => null  (A Little Salt is No Good...)
// Error Check Fails (Some Errors are not Caught)
pSBC ( 0.42, "#salt" ); // #salt + [42% Lighter] => #a5a5a500  (...and a Pound of Salt is Jibberish)
// Ripping
pSBCr ( color4 ); // #5567DAF0 + [Rip] => [object Object] => {'r':85,'g':103,'b':218,'a':0.941}
The picture below will help show the difference in the two blending methods:
Micro Functions
If you really want speed and size, you will have to use RGB not HEX. RGB is more straightforward and simple, HEX writes too slow and comes in too many flavors for a simple two-liner (IE. it could be a 3, 4, 6, or 8 digit HEX code). You will also need to sacrifice some features, no error checking, no HEX2RGB nor RGB2HEX. As well, you will need to choose a specific function (based on its function name below) for the color blending math, and if you want shading or blending. These functions do support alpha channels. And when both input colors have alphas it will Linear Blend them. If only one of the two colors has an alpha, it will pass it straight thru to the resulting color. Below are two liner functions that are incredibly fast and small:
const RGB_Linear_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+i(e[3]=="a"?e.slice(5):e.slice(4))*p)+","+r(i(b)*P+i(f)*p)+","+r(i(c)*P+i(g)*p)+j;
}
const RGB_Linear_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:255*p,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+t)+","+r(i(b)*P+t)+","+r(i(c)*P+t)+(d?","+d:")");
}
const RGB_Log_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+p*i(e[3]=="a"?e.slice(5):e.slice(4))**2)**0.5)+","+r((P*i(b)**2+p*i(f)**2)**0.5)+","+r((P*i(c)**2+p*i(g)**2)**0.5)+j;
}
const RGB_Log_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:p*255**2,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+t)**0.5)+","+r((P*i(b)**2+t)**0.5)+","+r((P*i(c)**2+t)**0.5)+(d?","+d:")");
}
Want more info? Read the full writeup on github.
PT
(P.s. If anyone has the math for another blending method, please share.)
I made a solution that works very nice for me:
function shadeColor(color, percent) {
var R = parseInt(color.substring(1,3),16);
var G = parseInt(color.substring(3,5),16);
var B = parseInt(color.substring(5,7),16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R<255)?R:255;
G = (G<255)?G:255;
B = (B<255)?B:255;
R = Math.round(R)
G = Math.round(G)
B = Math.round(B)
var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));
return "#"+RR+GG+BB;
}
Example Lighten:
shadeColor("#63C6FF",40);
Example Darken:
shadeColor("#63C6FF",-40);
Here is a super simple one liner based on Eric's answer
function adjust(color, amount) {
return '#' + color.replace(/^#/, '').replace(/../g, color => ('0'+Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
}
Examples:
adjust('#ffffff', -20) => "#ebebeb"
adjust('000000', 20) => "#141414"
I am adding my 2 cents here, a satisfyingly small combination of different answers:
const colorShade = (col, amt) => {
col = col.replace(/^#/, '')
if (col.length === 3) col = col[0] + col[0] + col[1] + col[1] + col[2] + col[2]
let [r, g, b] = col.match(/.{2}/g);
([r, g, b] = [parseInt(r, 16) + amt, parseInt(g, 16) + amt, parseInt(b, 16) + amt])
r = Math.max(Math.min(255, r), 0).toString(16)
g = Math.max(Math.min(255, g), 0).toString(16)
b = Math.max(Math.min(255, b), 0).toString(16)
const rr = (r.length < 2 ? '0' : '') + r
const gg = (g.length < 2 ? '0' : '') + g
const bb = (b.length < 2 ? '0' : '') + b
return `#${rr}${gg}${bb}`
}
accepts a color starting with # or not, with 6 characters or 3 characters.
example of use: colorShade('#54b946', -40)
Here is the output of 4 colors with 3 shades lighter and 3 shades darker for each of them (amount is a multiple of 40 here).
This is what I used based on your function. I prefer to use steps over percentage because it's more intuitive for me.
For example, 20% of a 200 blue value is much different than 20% of a 40 blue value.
Anyways, here's my modification, thanks for your original function.
function adjustBrightness(col, amt) {
var usePound = false;
if (col[0] == "#") {
col = col.slice(1);
usePound = true;
}
var R = parseInt(col.substring(0,2),16);
var G = parseInt(col.substring(2,4),16);
var B = parseInt(col.substring(4,6),16);
// to make the colour less bright than the input
// change the following three "+" symbols to "-"
R = R + amt;
G = G + amt;
B = B + amt;
if (R > 255) R = 255;
else if (R < 0) R = 0;
if (G > 255) G = 255;
else if (G < 0) G = 0;
if (B > 255) B = 255;
else if (B < 0) B = 0;
var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));
return (usePound?"#":"") + RR + GG + BB;
}
Based on the, David Sherret and Pablo, answer above converted the solution to safer version for Typescript
/**
* #param color Hex value format: #ffffff or ffffff
* #param decimal lighten or darken decimal value, example 0.5 to lighten by 50% or 1.5 to darken by 50%.
*/
static shadeColor(color: string, decimal: number): string {
const base = color.startsWith('#') ? 1 : 0;
let r = parseInt(color.substring(base, 3), 16);
let g = parseInt(color.substring(base + 2, 5), 16);
let b = parseInt(color.substring(base + 4, 7), 16);
r = Math.round(r / decimal);
g = Math.round(g / decimal);
b = Math.round(b / decimal);
r = (r < 255)? r : 255;
g = (g < 255)? g : 255;
b = (b < 255)? b : 255;
const rr = ((r.toString(16).length === 1)? `0${r.toString(16)}` : r.toString(16));
const gg = ((g.toString(16).length === 1)? `0${g.toString(16)}` : g.toString(16));
const bb = ((b.toString(16).length === 1)? `0${b.toString(16)}` : b.toString(16));
return `#${rr}${gg}${bb}`;
}
I tried your function and there was a little bug: If some final 'r' value is 1 digit only, the result comes up like: 'a0a0a' when the right value is '0a0a0a', for example.
I just quick-fixed it by adding this instead of your return:
var rStr = (r.toString(16).length < 2)?'0'+r.toString(16):r.toString(16);
var gStr = (g.toString(16).length < 2)?'0'+g.toString(16):g.toString(16);
var bStr = (b.toString(16).length < 2)?'0'+b.toString(16):b.toString(16);
return (usePound?"#":"") + rStr + gStr + bStr;
Maybe it's not so nice but it do the work. Great function, BTW. Just what I needed. :)
have you thought about an rgb > hsl conversion? then just move the Luminosity up and down? thats the way I would go.
A quick look for some algorithms got me the following sites.
PHP:
http://serennu.com/colour/rgbtohsl.php
Javascript:
http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
EDIT the above link is no longer valid. You can view git hub for the page source or the gist
Alternatively another StackOverflow question might be a good place to look.
Even though this is not the right choice for the OP the following is an approximation of the code I was originally suggesting. (Assuming you have rgb/hsl conversion functions)
var SHADE_SHIFT_AMOUNT = 0.1;
function lightenShade(colorValue)
{
if(colorValue && colorValue.length >= 6)
{
var redValue = parseInt(colorValue.slice(-6,-4), 16);
var greenValue = parseInt(colorValue.slice(-4,-2), 16);
var blueValue = parseInt(colorValue.slice(-2), 16);
var hsl = rgbToHsl(redValue, greenValue, blueValue);
hsl[2]= Math.min(hsl[2] + SHADE_SHIFT_AMOUNT, 1);
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
}
return null;
}
function darkenShade(colorValue)
{
if(colorValue && colorValue.length >= 6)
{
var redValue = parseInt(colorValue.slice(-6,-4), 16);
var greenValue = parseInt(colorValue.slice(-4,-2), 16);
var blueValue = parseInt(colorValue.slice(-2), 16);
var hsl = rgbToHsl(redValue, greenValue, blueValue);
hsl[2]= Math.max(hsl[2] - SHADE_SHIFT_AMOUNT, 0);
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
}
return null;
}
This assumes:
You have functions hslToRgb and rgbToHsl.
The parameter colorValue is a string in the form #RRGGBB
Although if we are discussing css there is a syntax for specifying hsl/hsla for IE9/Chrome/Firefox.
I wanted to change a color to a specific brightness level - no matter what brightness the color was before - here's a simple JS function that seems to work well, although I'm sure it could be shorter
function setLightPercentage(col: any, p: number) {
const R = parseInt(col.substring(1, 3), 16);
const G = parseInt(col.substring(3, 5), 16);
const B = parseInt(col.substring(5, 7), 16);
const curr_total_dark = (255 * 3) - (R + G + B);
// calculate how much of the current darkness comes from the different channels
const RR = ((255 - R) / curr_total_dark);
const GR = ((255 - G) / curr_total_dark);
const BR = ((255 - B) / curr_total_dark);
// calculate how much darkness there should be in the new color
const new_total_dark = ((255 - 255 * (p / 100)) * 3);
// make the new channels contain the same % of available dark as the old ones did
const NR = 255 - Math.round(RR * new_total_dark);
const NG = 255 - Math.round(GR * new_total_dark);
const NB = 255 - Math.round(BR * new_total_dark);
const RO = ((NR.toString(16).length === 1) ? "0" + NR.toString(16) : NR.toString(16));
const GO = ((NG.toString(16).length === 1) ? "0" + NG.toString(16) : NG.toString(16));
const BO = ((NB.toString(16).length === 1) ? "0" + NB.toString(16) : NB.toString(16));
return "#" + RO + GO + BO;}
Your approach is ok :) I simplify your shortest version a little (for saturation control look here)
(col,amt)=> (+('0x'+col)+amt*0x010101).toString(16).padStart(6,0)
// Similar to OP shortest version, we not have here # and colors range checking
var LightenDarkenColor =
(col,amt) => (+('0x'+col)+amt*0x010101).toString(16).padStart(6,0);
// ------
// TEST
// ------
function update() {
let c= col.value.padEnd(6,'0').slice(0,6);
let color = '#'+LightenDarkenColor(c, +amt.value);
oldColor.innerHTML = 'Old: #'+c;
oldColor.style = `background: #${c}`;
newColor.innerHTML = 'New: '+color
newColor.style = `background: ${color}`;
}
update();
.box{ width: 100px; height: 100px; margin: 10px; display: inline-block}
<input id="col" value="3F6D2A" oninput="update()">
<input id="amt" value="30" oninput="update()"><br>
<div id="oldColor" class="box"></div>
<div id="newColor" class="box"></div>
And version with # and color ranges checking
// # and colors range checking
var LightenDarkenColor =
(col,amt) => '#'+col.slice(1).match(/../g)
.map(x=>(x=+`0x${x}`+amt,x<0?0:(x>255?255:x))
.toString(16).padStart(2,0)).join``;
// ------
// TEST
// ------
function update() {
let c= col.value.padEnd(6,'0').slice(0,7);
let color = LightenDarkenColor(c, +amt.value);
oldColor.innerHTML = 'Old: '+c;
oldColor.style = `background: ${c}`;
newColor.innerHTML = 'New: '+color
newColor.style = `background: ${color}`;
}
update();
.box{ width: 100px; height: 100px; margin: 10px; display: inline-block}
<input id="col" value="#3F6D2A" oninput="update()">
<input id="amt" value="40" oninput="update()"><br>
<div id="oldColor" class="box"></div>
<div id="newColor" class="box"></div>
C# Version...
note that I am getting color strings in this format #FF12AE34, and need to cut out the #FF.
private string GetSmartShadeColorByBase(string s, float percent)
{
if (string.IsNullOrEmpty(s))
return "";
var r = s.Substring(3, 2);
int rInt = int.Parse(r, NumberStyles.HexNumber);
var g = s.Substring(5, 2);
int gInt = int.Parse(g, NumberStyles.HexNumber);
var b = s.Substring(7, 2);
int bInt = int.Parse(b, NumberStyles.HexNumber);
var t = percent < 0 ? 0 : 255;
var p = percent < 0 ? percent*-1 : percent;
int newR = Convert.ToInt32(Math.Round((t - rInt) * p) + rInt);
var newG = Convert.ToInt32(Math.Round((t - gInt) * p) + gInt);
var newB = Convert.ToInt32(Math.Round((t - bInt) * p) + bInt);
return String.Format("#{0:X2}{1:X2}{2:X2}", newR, newG, newB);
}
I just used the hex number preceded by '#'.
var x = 0xf0f0f0;
x=x+0xf00; //set this value as you wish programatically
document.getElementById("heading").style = 'background-color: #'+x.toString(16);
higher the number ..lighter the color
I needed it in C#, it may help .net developers
public static string LightenDarkenColor(string color, int amount)
{
int colorHex = int.Parse(color, System.Globalization.NumberStyles.HexNumber);
string output = (((colorHex & 0x0000FF) + amount) | ((((colorHex >> 0x8) & 0x00FF) + amount) << 0x8) | (((colorHex >> 0xF) + amount) << 0xF)).ToString("x6");
return output;
}
My version written in typescript:
function changeColorLightness(color: number, lightness: number): number {
return (Math.max(0, Math.min(((color & 0xFF0000) / 0x10000) + lightness, 0xFF)) * 0x10000) +
(Math.max(0, Math.min(((color & 0x00FF00) / 0x100) + lightness, 0xFF)) * 0x100) +
(Math.max(0, Math.min(((color & 0x0000FF)) + lightness, 0xFF)));
}
explanation:
export function changeColorLightness(color: number, lightness: number): number {
const r = (color & 0xFF0000) / 0x10**4;
const g = (color & 0x00FF00) / 0x10**2;
const b = (color & 0x0000FF);
const changedR = Math.max(0, Math.min(r + lightness, 0xFF));
const changedG = Math.max(0, Math.min(g + lightness, 0xFF));
const changedB = Math.max(0, Math.min(b + lightness, 0xFF));
return (changedR * 0x10**4) + (changedG * 0x10**2) + changedB;
}
usage:
changeColorLightness(0x00FF00, 0x50);
changeColorLightness(parseInt("#00FF00".replace('#',''), 16), 0x50);
changeColorLightness(0x00FF00, 127.5);
The following method will allow you to lighten or darken the exposure value of a Hexadecimal (Hex) color string:
private static string GetHexFromRGB(byte r, byte g, byte b, double exposure)
{
exposure = Math.Max(Math.Min(exposure, 1.0), -1.0);
if (exposure >= 0)
{
return "#"
+ ((byte)(r + ((byte.MaxValue - r) * exposure))).ToString("X2")
+ ((byte)(g + ((byte.MaxValue - g) * exposure))).ToString("X2")
+ ((byte)(b + ((byte.MaxValue - b) * exposure))).ToString("X2");
}
else
{
return "#"
+ ((byte)(r + (r * exposure))).ToString("X2")
+ ((byte)(g + (g * exposure))).ToString("X2")
+ ((byte)(b + (b * exposure))).ToString("X2");
}
}
For the last parameter value in GetHexFromRGB(), Pass in a double value somewhere between -1 and 1 (-1 is black, 0 is unchanged, 1 is white):
// split color (#e04006) into three strings
var r = Convert.ToByte("e0", 16);
var g = Convert.ToByte("40", 16);
var b = Convert.ToByte("06", 16);
GetHexFromRGB(r, g, b, 0.25); // Lighten by 25%;
There is lack of support for colors starting from 00 ie "#000623" but here is the fix
function lightenDarkenColor(colorCode, amount) {
let usePound = false;
if (colorCode[0] == "#") {
colorCode = colorCode.slice(1);
usePound = true;
}
const num = parseInt(colorCode, 16);
let r = (num >> 16) + amount;
if (r > 255) {
r = 255;
} else if (r < 0) {
r = 0;
}
let b = ((num >> 8) & 0x00FF) + amount;
if (b > 255) {
b = 255;
} else if (b < 0) {
b = 0;
}
let g = (num & 0x0000FF) + amount;
if (g > 255) {
g = 255;
} else if (g < 0) {
g = 0;
}
let color = (g | (b << 8) | (r << 16)).toString(16);
while (color.length < 6){
color = 0 + color;
}
return (usePound ? '#' : '') + color;
}
I've rewritten this answer https://stackoverflow.com/a/13542669/4537906 into a pair of readable functions in TypeScript.
The reason is that in modern JavaScript we don't need to care about saving characters any more. That is done by the compilers. IMO we should aim for a readable and understandable code.
This is my approach:
tint.ts
type ColorObject = Record<"r" | "g" | "b" | "a", number>;
const singleColorSpace = 16 * 16; // 256
const blueSpace = singleColorSpace;
const greenSpace = blueSpace * singleColorSpace; // 65536
const redSpace = greenSpace * singleColorSpace; // 16777216
/* eslint-disable regex/invalid */
// adapted to TS from https://github.com/PimpTrizkit/PJs/wiki/12.-Shade,-Blend-and-Convert-a-Web-Color-(pSBC.js)
export const toColorObject = (rgbOrHex: string): ColorObject => {
const { length } = rgbOrHex;
const outputColor = {} as ColorObject;
if (length > 9) {
const rgbaColor = rgbOrHex.split(",");
const [rgbaAndRed, green, blue, alpha] = rgbaColor;
if (rgbaAndRed.slice(0, 3) !== "rgb") {
throw new Error("Invalid color format");
}
const red = rgbaAndRed[3] === "a" ? rgbaAndRed.slice(5) : rgbaAndRed.slice(4);
const rgbaLength = rgbaColor.length;
if (rgbaLength < 3 || rgbaLength > 4) {
return null;
}
outputColor.r = parseInt(red, 10);
outputColor.g = parseInt(green, 10);
outputColor.b = parseInt(blue, 10);
outputColor.a = alpha ? parseFloat(alpha) : -1;
} else {
if (length === 8 || length === 6 || length < 4) {
throw new Error("Invalid hex color format");
}
let HexColor = rgbOrHex;
if (length < 6) {
HexColor = `#${rgbOrHex[1]}${rgbOrHex[1]}${rgbOrHex[2]}${rgbOrHex[2]}${rgbOrHex[3]}${rgbOrHex[3]}${
length > 4 ? rgbOrHex[4] + rgbOrHex[4] : ""
}`;
}
if (length === 9 || length === 5) {
const hexRed = parseInt(HexColor.slice(1, 3), 16);
outputColor.r = hexRed;
const hexGreen = parseInt(HexColor.slice(3, 5), 16);
outputColor.g = hexGreen;
const hexBlue = parseInt(HexColor.slice(5, 7), 16);
outputColor.b = hexBlue;
const hexAlpha = parseInt(HexColor.slice(7, 9), 16);
outputColor.a = Math.round((hexAlpha / 255) * 100) / 100;
} else {
const hexRed = parseInt(HexColor.slice(1, 3), 16);
outputColor.r = hexRed;
const hexGreen = parseInt(HexColor.slice(3, 5), 16);
outputColor.g = hexGreen;
const hexBlue = parseInt(HexColor.slice(5, 7), 16);
outputColor.b = hexBlue;
outputColor.a = -1;
}
}
return outputColor;
};
const black: ColorObject = { r: 0, g: 0, b: 0, a: -1 };
const white: ColorObject = { r: 255, g: 255, b: 255, a: -1 };
export const tint = (
ratio: number,
inputColor: string,
{ toColor, useLinear, reformat }: { toColor?: string; useLinear?: boolean; reformat?: boolean } = {}
) => {
const { round } = Math;
const clampedRatio = Math.min(Math.max(ratio, -1), 1);
if (ratio < -1 || ratio > 1) {
// eslint-disable-next-line no-console
console.info(`Ratio should be between -1 and 1 and it is ${ratio}. It will be clamped to ${clampedRatio}`);
}
let baseColor = inputColor;
if (inputColor[0] !== "r" && inputColor[0] !== "#") {
baseColor = "#000";
// eslint-disable-next-line no-console
console.info(
`Invalid input color format. "${inputColor}" should be rgb(a) or hex. It will fallback to "${baseColor}"`
);
}
let isRGBformat = baseColor.length > 9 || baseColor.includes("rgb(");
isRGBformat = reformat ? !isRGBformat : isRGBformat;
if (toColor) {
const isToColorRgbFormat = (toColor && toColor?.length > 9) || toColor?.includes("rgb(");
isRGBformat = reformat ? !isToColorRgbFormat : isToColorRgbFormat;
}
const formattedBaseColor = toColorObject(baseColor);
const isNegativeRatio = clampedRatio < 0;
const toColorDefault = isNegativeRatio ? black : white;
const formattedToColor = toColor && !reformat ? toColorObject(toColor) : toColorDefault;
const toColorRatio = Math.abs(clampedRatio);
const baseRatio = 1 - toColorRatio;
const outputColor = {} as ColorObject;
if (useLinear) {
outputColor.r = round(baseRatio * formattedBaseColor.r + toColorRatio * formattedToColor.r);
outputColor.g = round(baseRatio * formattedBaseColor.g + toColorRatio * formattedToColor.g);
outputColor.b = round(baseRatio * formattedBaseColor.b + toColorRatio * formattedToColor.b);
} else {
outputColor.r = round((baseRatio * formattedBaseColor.r ** 2 + toColorRatio * formattedToColor.r ** 2) ** 0.5);
outputColor.g = round((baseRatio * formattedBaseColor.g ** 2 + toColorRatio * formattedToColor.g ** 2) ** 0.5);
outputColor.b = round((baseRatio * formattedBaseColor.b ** 2 + toColorRatio * formattedToColor.b ** 2) ** 0.5);
}
const blendedAlpha = formattedBaseColor.a * baseRatio + formattedToColor.a * toColorRatio;
outputColor.a = formattedToColor.a < 0 ? formattedBaseColor.a : blendedAlpha;
const hasAlpha = formattedBaseColor.a >= 0 || formattedToColor.a >= 0;
if (isRGBformat) {
return `rgb${hasAlpha ? "a" : ""}(${outputColor.r},${outputColor.g},${outputColor.b}${
hasAlpha ? `,${round(outputColor.a * 1000) / 1000}` : ""
})`;
}
return `#${(
outputColor.r * redSpace +
outputColor.g * greenSpace +
outputColor.b * blueSpace +
(hasAlpha ? round(outputColor.a * 255) : 0)
)
.toString(16)
// If no Alpha, we remove the last 2 hex digits
.slice(0, hasAlpha ? undefined : -2)}`;
};
And also a collection of jest tests
tint.test.ts
import { tint, toColorObject } from "./tint";
const rgbBlue = "rgb(20,60,200)";
const rgbaBlue = "rgba(20,60,200,0.67423)";
const hex6Cyan = "#67DAF0";
const hex3Pink = "#F3A";
const hex4Pink = "#F3A9";
const rbgBrown = "rgb(200,60,20)";
const rgbaBrown = "rgba(200,60,20,0.98631)";
describe("tint", () => {
describe("Logarithmic blending", () => {
describe("Shades", () => {
it("lightens rgb color", () => {
expect(tint(0.42, rgbBlue)).toEqual("rgb(166,171,225)");
});
it("darkens hex color", () => {
expect(tint(-0.4, hex3Pink)).toEqual("#c62884");
});
it("lightens rgba color", () => {
expect(tint(0.42, rgbaBrown)).toEqual("rgba(225,171,166,0.986)");
});
it("returns black with ratio -1", () => {
expect(tint(-1, rgbBlue)).toEqual("rgb(0,0,0)");
});
});
describe("converts color notation", () => {
it("converts from rgba to hexa", () => {
// expect(tint(0.42, color2, "c")).toEqual("#a6abe1ac");
expect(tint(0.42, rgbaBlue, { reformat: true })).toEqual("#a6abe1ac");
});
it("converts from hexa to rgba", () => {
// expect(tint(0, color6, "c", true)).toEqual("rgba(255,51,170,0.6)");
expect(tint(0, hex4Pink, { reformat: true })).toEqual("rgba(255,51,170,0.6)");
});
it("converts and returns white with ratio 1", () => {
expect(tint(1, hex3Pink, { reformat: true })).toEqual("rgb(255,255,255)");
});
});
describe("Blends two colors", () => {
it("blends rgba with rgba", () => {
expect(tint(-0.5, rgbaBlue, { toColor: rgbaBrown })).toEqual("rgba(142,60,142,0.83)");
});
it("blends rgba with rgb", () => {
expect(tint(0.7, rgbaBlue, { toColor: rbgBrown })).toEqual("rgba(168,60,111,0.674)");
});
it("blends hex with rgb", () => {
expect(tint(0.25, hex6Cyan, { toColor: rbgBrown })).toEqual("rgb(134,191,208)");
});
it("blends rgb with hex", () => {
expect(tint(0.75, rbgBrown, { toColor: hex6Cyan })).toEqual("#86bfd0");
});
});
});
describe("Linear Blending", () => {
describe("Shades", () => {
it("lightens rgb color", () => {
expect(tint(0.42, rgbBlue, { useLinear: true })).toEqual("rgb(119,142,223)");
});
it("darkens hex color", () => {
expect(tint(-0.4, hex3Pink, { useLinear: true })).toEqual("#991f66");
});
it("lightens rgba color", () => {
expect(tint(0.42, rgbaBrown, { useLinear: true })).toEqual("rgba(223,142,119,0.986)");
});
it("returns black with ratio -1", () => {
expect(tint(-1, rgbBlue, { useLinear: true })).toEqual("rgb(0,0,0)");
});
});
describe("converts color notation", () => {
it("converts from rgba to hexa", () => {
expect(tint(0.42, rgbaBlue, { reformat: true, useLinear: true })).toEqual("#778edfac");
});
it("converts from hexa to rgba", () => {
expect(tint(0, hex4Pink, { reformat: true, useLinear: true })).toEqual("rgba(255,51,170,0.6)");
});
it("converts and returns white with ratio 1", () => {
expect(tint(1, hex3Pink, { useLinear: true, reformat: true })).toEqual("rgb(255,255,255)");
});
});
describe("Blends two colors", () => {
it("blends rgba with rgba", () => {
expect(tint(-0.5, rgbaBlue, { toColor: rgbaBrown, useLinear: true })).toEqual("rgba(110,60,110,0.83)");
});
it("blends rgba with rgb", () => {
expect(tint(0.7, rgbaBlue, { toColor: rbgBrown, useLinear: true })).toEqual("rgba(146,60,74,0.674)");
});
it("blends hex with rgb", () => {
expect(tint(0.25, hex6Cyan, { toColor: rbgBrown, useLinear: true })).toEqual("rgb(127,179,185)");
});
it("blends rgb with hex", () => {
expect(tint(0.75, rbgBrown, { toColor: hex6Cyan, useLinear: true })).toEqual("#7fb3b9");
});
});
});
describe("Error handling", () => {
describe("When invalid hex color provided", () => {
it.each([1, 2, 5])("throws error if hex color has %s characters", (n) => {
const correlativeNumbers = Array.from(Array(n).keys()).join("");
expect(() => tint(0, `#${correlativeNumbers}`)).toThrow("Invalid hex color format");
});
});
describe("When ratio is not between -1 and 1", () => {
it("clamps ratio to -1", () => {
expect(tint(-43, rgbBlue)).toEqual("rgb(0,0,0)");
});
it("clamps ratio to 1", () => {
expect(tint(42, rgbBlue)).toEqual("rgb(255,255,255)");
});
});
});
});
describe("toColorObject function", () => {
it("should return a color object from hex", () => {
expect(toColorObject("#fff")).toEqual({
r: 255,
g: 255,
b: 255,
a: -1,
});
});
it("should return a color object from hex with alpha", () => {
expect(toColorObject("#fff6")).toEqual({
r: 255,
g: 255,
b: 255,
a: 0.4,
});
});
it("should return a color object from rgb", () => {
expect(toColorObject("rgb(255,255,255)")).toEqual({
r: 255,
g: 255,
b: 255,
a: -1,
});
});
it("should return a color object from rgba", () => {
expect(toColorObject("rgba(255,255,255,1)")).toEqual({
r: 255,
g: 255,
b: 255,
a: 1,
});
});
describe("Error handling", () => {
it("should throw error if invalid color provided", () => {
expect(() => toColorObject("foo")).toThrow("Invalid hex color format");
});
it("should throw error if invalid color provided", () => {
expect(() => toColorObject("invalid color")).toThrow("Invalid color format");
});
});
});
I hope you enjoy it. It's very simple but it works pretty fine
I made a port of the excellent xcolor library to remove its jQuery dependency. There are a ton of functions in there including lightening and darkening colors.
Really, converting hex to RGB is a completely separate function from lightening or darkening colors. Keep things DRY please. In any case, once you have an RGB color, you can just add the difference between the light level you want and the light level you have to each of the RGB values:
var lightness = function(level) {
if(level === undefined) {
return Math.max(this.g,this.r,this.b)
} else {
var roundedLevel = Math.round(level) // fractions won't work here
var levelChange = roundedLevel - this.lightness()
var r = Math.max(0,this.r+levelChange)
var g = Math.max(0,this.g+levelChange)
var b = Math.max(0,this.b+levelChange)
if(r > 0xff) r = 0xff
if(g > 0xff) g = 0xff
if(b > 0xff) b = 0xff
return xolor({r: r, g: g, b: b})
}
}
var lighter = function(amount) {
return this.lightness(this.lightness()+amount)
}
See https://github.com/fresheneesz/xolor for more of the source.
I've long wanted to be able to produce tints/shades of colours, here is my JavaScript solution:
const varyHue = function (hueIn, pcIn) {
const truncate = function (valIn) {
if (valIn > 255) {
valIn = 255;
} else if (valIn < 0) {
valIn = 0;
}
return valIn;
};
let red = parseInt(hueIn.substring(0, 2), 16);
let green = parseInt(hueIn.substring(2, 4), 16);
let blue = parseInt(hueIn.substring(4, 6), 16);
let pc = parseInt(pcIn, 10); //shade positive, tint negative
let max = 0;
let dif = 0;
max = red;
if (pc < 0) { //tint: make lighter
if (green < max) {
max = green;
}
if (blue < max) {
max = blue;
}
dif = parseInt(((Math.abs(pc) / 100) * (255 - max)), 10);
return leftPad(((truncate(red + dif)).toString(16)), '0', 2) + leftPad(((truncate(green + dif)).toString(16)), '0', 2) + leftPad(((truncate(blue + dif)).toString(16)), '0', 2);
} else { //shade: make darker
if (green > max) {
max = green;
}
if (blue > max) {
max = blue;
}
dif = parseInt(((pc / 100) * max), 10);
return leftPad(((truncate(red - dif)).toString(16)), '0', 2) + leftPad(((truncate(green - dif)).toString(16)), '0', 2) + leftPad(((truncate(blue - dif)).toString(16)), '0', 2);
}
};
thank all for all good answer and edits, they help me to I develop this helpful solution, as I think.
/**
* #param color Hex value format: #ffffff or ffffff or fff or ffffff00 with opacity or rgb rgba formats
* #param decimal lighten or darken decimal value, example -0.5 to darken by 50% or 0.5 to lighten by 50%.
*/
function shadeColor(color, decimal){
if(typeof color!=="string" || !color)return color;
if(typeof decimal !== "number" || !decimal)return color;
var r,g,b;
var _format="";
color=color.trim();
if(/^rgba?\((\s*[0-9]{1,3}\s*)(,\s*[0-9]{1,3}\s*){2}(,\s*[0-9](\.[0-9]+)?\s*)?\)$/i.test(color)){
color=color.replace(/[a-z)(\s]+/ig,'').split(/,/)
.map(function (c,i) { return ((i===3)?c:parseInt(c)); });
_format="rgb";
if(color.length===4) {
_format += "a(VAL," + color[3]+")";
color.pop();
}else _format+="(VAL)";
}else if(/^#?([a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})$/i.test(color)){
var group=color.length>5?2:1;
color=color.replace("#","")
.split(new RegExp("([a-f0-9]{"+group.toString()+"})",'i'))
.filter(function(c){return c.length===group;})
.map(function (c,i) { if(group===1)c+=c;return ((i===3)?c:parseInt(c,16)); });
_format="#VAL";
if(color.length===4) {
_format += color[3];
color.pop();
}
}else return color;
if(decimal<=1 && decimal>=-1)decimal*=100;
color=color.reduce(function (_c,c) {
c=Math.round(c * (100 + decimal) / 100);
c= (c < 255)? c : 255;
if(/^#/.test(_format)){
c=c.toString(16);
if(c.length === 1)c="0"+c;
return _c+c;
}
if(_c==="")return c;
return _c+","+c;
},"");
return _format.replace("VAL",color);
}
You Can Use to dark or light, example:
//lighten
shadeColor("#abcdef",0.5);
shadeColor("#abcdef",0.3);
shadeColor("#abcdef",50);
shadeColor("#abcdef",30);
//it's work with all color format supported in css3 and transparent future
//for example transparnet
shadeColor("#abc8",0.5);
shadeColor("#abcdefff",0.5);
shadeColor("#abcf",35);
//for example rgb and rgba
shadeColor("rgb(128,33,244)",0.5);
shadeColor("rgba(128,33,244,0.3)",0.5);
//darken like above except the decimal with negative number (less than 0)
shadeColor("#abcdef",-0.5);
shadeColor("#abcdef",-50);
shadeColor("rgb(128,33,244)",-35);
How to simple shade color in PHP?
<?php
function shadeColor ($color='#cccccc', $percent=-25) {
$color = Str_Replace("#",Null,$color);
$r = Hexdec(Substr($color,0,2));
$g = Hexdec(Substr($color,2,2));
$b = Hexdec(Substr($color,4,2));
$r = (Int)($r*(100+$percent)/100);
$g = (Int)($g*(100+$percent)/100);
$b = (Int)($b*(100+$percent)/100);
$r = Trim(Dechex(($r<255)?$r:255));
$g = Trim(Dechex(($g<255)?$g:255));
$b = Trim(Dechex(($b<255)?$b:255));
$r = ((Strlen($r)==1)?"0{$r}":$r);
$g = ((Strlen($g)==1)?"0{$g}":$g);
$b = ((Strlen($b)==1)?"0{$b}":$b);
return (String)("#{$r}{$g}{$b}");
}
echo shadeColor(); // #999999

Categories

Resources