Reputation: 449425
Does anybody know, off the top of your heads, a Javascript solution for calculating the complementary colour of a hex value?
There are a number of colour picking suites and palette generators on the web but I haven't seen any that actually calculate the colour dynamically using JS.
A detailed hint or a snippet would be very much appreciated.
Upvotes: 71
Views: 58621
Reputation: 392
Adding onto
Blockquote
Adding onto this answer, I had hex strings in the form '#DDDDDD' which required some type wrangling
const hex: number = parseInt(color.slice(-6), 16);
const complement = 0xffffff ^ hex;
const hexColor = '#' + ('000000' + complement.toString(16)).slice(-6);
Upvotes: 0
Reputation: 117
Usefuls simple and elegant solution, but unfortunately rgb complement does not work well for black, i.e., rgb(0,0,0). Needed this for Bootstrap cards created with JS loop for all colors in provided in "json-color-names.json", so instead of the given solution, just calculated rgb components as: 255-r, 255-g, 255-b, or simply by using template literals:
return `rgb(${255-r},${255-g},${255-b})`;
But, this one doesn't give good result for gray rgb(128,128,128). So, I'd say that satisfactory results are achievable with other more complicated solutions or libraries, of which some are mentioned above, but when seeking for short and simple solution just to achieve text color different from the background, this might be the one, with just some formula tweaking (maybe by using mod or some other function).
Upvotes: 0
Reputation: 8537
There's a much simpler way to get the complementary color than converting back and forth between RGB and HSV/HSL (same results though):
function complement(r, g, b) {
const m = Math.min(r, g, b) + Math.max(r, g, b);
return [m - r, m - g, m - b];
}
All the way from hex:
function hexComplement(rgb) {
rgb = Number.parseInt(rgb.substring(1), 16);
const [r, g, b] = complement(rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff);
return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
}
Upvotes: 2
Reputation: 41
Hex and RGB complementry This is most correct and efficient way to get complementary hex color value
function complementryHexColor(hex){
let r = hex.length == 4 ? parseInt(hex[1] + hex[1], 16) : parseInt(hex.slice(1, 3), 16);
let g = hex.length == 4 ? parseInt(hex[2] + hex[2], 16) : parseInt(hex.slice(3, 5), 16);
let b = hex.length == 4 ? parseInt(hex[3] + hex[3], 16) : parseInt(hex.slice(5), 16);
[r, g, b] = complementryRGBColor(r, g, b);
return '#' + (r < 16 ? '0' + r.toString(16) : r.toString(16)) + (g < 16 ? '0' + g.toString(16) : g.toString(16)) + (b < 16 ? '0' + b.toString(16) : b.toString(16));
}
function complementryRGBColor(r, g, b) {
if (Math.max(r, g, b) == Math.min(r, g, b)) {
return [255 - r, 255 - g, 255 - b];
} else {
r /= 255, g /= 255, b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h = Math.round((h*60) + 180) % 360;
h /= 360;
function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
return [Math.round(r*255), Math.round(g*255), Math.round(b*255)];
}
}
Upvotes: 4
Reputation: 18803
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function rgbComplimentary(r,g,b){
var hex = "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')';
// Get array of RGB values
rgb = rgb.replace(/[^\d,]/g, '').split(',');
var r = rgb[0]/255.0, g = rgb[1]/255.0, b = rgb[2]/255.0;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, l = (max + min) / 2.0;
if(max == min) {
h = s = 0; //achromatic
} else {
var d = max - min;
s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));
if(max == r && g >= b) {
h = 1.0472 * (g - b) / d ;
} else if(max == r && g < b) {
h = 1.0472 * (g - b) / d + 6.2832;
} else if(max == g) {
h = 1.0472 * (b - r) / d + 2.0944;
} else if(max == b) {
h = 1.0472 * (r - g) / d + 4.1888;
}
}
h = h / 6.2832 * 360.0 + 0;
// Shift hue to opposite side of wheel and convert to [0-1] value
h+= 180;
if (h > 360) { h -= 360; }
h /= 360;
// Convert h s and l values into r g and b values
// Adapted from answer by Mohsen http://stackoverflow.com/a/9493060/4939630
if(s === 0){
r = g = b = l; // achromatic
} else {
var hue2rgb = function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
// Convert r b and g values to hex
rgb = b | (g << 8) | (r << 16);
return hexToRgb("#" + (0x1000000 | rgb).toString(16).substring(1));
}
console.log(rgbComplimentary(242, 211, 215));
function hexComplimentary(hex){
var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')';
// Get array of RGB values
rgb = rgb.replace(/[^\d,]/g, '').split(',');
var r = rgb[0]/255.0, g = rgb[1]/255.0, b = rgb[2]/255.0;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, l = (max + min) / 2.0;
if(max == min) {
h = s = 0; //achromatic
} else {
var d = max - min;
s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));
if(max == r && g >= b) {
h = 1.0472 * (g - b) / d ;
} else if(max == r && g < b) {
h = 1.0472 * (g - b) / d + 6.2832;
} else if(max == g) {
h = 1.0472 * (b - r) / d + 2.0944;
} else if(max == b) {
h = 1.0472 * (r - g) / d + 4.1888;
}
}
h = h / 6.2832 * 360.0 + 0;
// Shift hue to opposite side of wheel and convert to [0-1] value
h+= 180;
if (h > 360) { h -= 360; }
h /= 360;
if(s === 0){
r = g = b = l; // achromatic
} else {
var hue2rgb = function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
// Convert r b and g values to hex
rgb = b | (g << 8) | (r << 16);
return "#" + (0x1000000 | rgb).toString(16).substring(1);
}
console.log(hexComplimentary("#ff5a5a"));
Source: Updated https://stackoverflow.com/a/37657940/6569224 answer
Upvotes: 3
Reputation: 67822
Parsed through http://design.geckotribe.com/colorwheel/
// Complement
temprgb={ r: 0, g: 0xff, b: 0xff }; // Cyan
temphsv=RGB2HSV(temprgb);
temphsv.hue=HueShift(temphsv.hue,180.0);
temprgb=HSV2RGB(temphsv);
console.log(temprgb); // Complement is red (0xff, 0, 0)
function RGB2HSV(rgb) {
hsv = new Object();
max=max3(rgb.r,rgb.g,rgb.b);
dif=max-min3(rgb.r,rgb.g,rgb.b);
hsv.saturation=(max==0.0)?0:(100*dif/max);
if (hsv.saturation==0) hsv.hue=0;
else if (rgb.r==max) hsv.hue=60.0*(rgb.g-rgb.b)/dif;
else if (rgb.g==max) hsv.hue=120.0+60.0*(rgb.b-rgb.r)/dif;
else if (rgb.b==max) hsv.hue=240.0+60.0*(rgb.r-rgb.g)/dif;
if (hsv.hue<0.0) hsv.hue+=360.0;
hsv.value=Math.round(max*100/255);
hsv.hue=Math.round(hsv.hue);
hsv.saturation=Math.round(hsv.saturation);
return hsv;
}
// RGB2HSV and HSV2RGB are based on Color Match Remix [http://color.twysted.net/]
// which is based on or copied from ColorMatch 5K [http://colormatch.dk/]
function HSV2RGB(hsv) {
var rgb=new Object();
if (hsv.saturation==0) {
rgb.r=rgb.g=rgb.b=Math.round(hsv.value*2.55);
} else {
hsv.hue/=60;
hsv.saturation/=100;
hsv.value/=100;
i=Math.floor(hsv.hue);
f=hsv.hue-i;
p=hsv.value*(1-hsv.saturation);
q=hsv.value*(1-hsv.saturation*f);
t=hsv.value*(1-hsv.saturation*(1-f));
switch(i) {
case 0: rgb.r=hsv.value; rgb.g=t; rgb.b=p; break;
case 1: rgb.r=q; rgb.g=hsv.value; rgb.b=p; break;
case 2: rgb.r=p; rgb.g=hsv.value; rgb.b=t; break;
case 3: rgb.r=p; rgb.g=q; rgb.b=hsv.value; break;
case 4: rgb.r=t; rgb.g=p; rgb.b=hsv.value; break;
default: rgb.r=hsv.value; rgb.g=p; rgb.b=q;
}
rgb.r=Math.round(rgb.r*255);
rgb.g=Math.round(rgb.g*255);
rgb.b=Math.round(rgb.b*255);
}
return rgb;
}
//Adding HueShift via Jacob (see comments)
function HueShift(h,s) {
h+=s; while (h>=360.0) h-=360.0; while (h<0.0) h+=360.0; return h;
}
//min max via Hairgami_Master (see comments)
function min3(a,b,c) {
return (a<b)?((a<c)?a:c):((b<c)?b:c);
}
function max3(a,b,c) {
return (a>b)?((a>c)?a:c):((b>c)?b:c);
}
Upvotes: 69
Reputation: 7314
Rather than reinventing the wheel, I found a library to work with colors.
This is how you would implement some of the other answers using it.
color1 = tinycolor2('#f00').spin(180).toHexString(); // Hue Shift
color2 = tinycolor2("#f00").complement().toHexString(); // bitwise
Upvotes: 12
Reputation: 577
I find that taking the bit-wise complement works well, and quickly.
var color = 0x320ae3;
var complement = 0xffffff ^ color;
I'm not sure if it's a perfect complement in the sense of "mixes together to form a 70% grey", however a 70% grey is "pure white" in terms of color timing in film. It occurred to me that XORing the RGB hex out of pure white might be a good first approximation. You could also try a darker grey to see how that works for you.
Again, this is a fast approximation and I make no guarantees that it'll be perfectly accurate.
See https://github.com/alfl/textful/blob/master/app.js#L38 for my implementation.
Upvotes: 40
Reputation: 5238
None of the other functions here worked out the box, so I made this one.
It takes a hex value, converts it to HSL, shifts the hue 180 degrees and converts back to Hex
/* hexToComplimentary : Converts hex value to HSL, shifts
* hue by 180 degrees and then converts hex, giving complimentary color
* as a hex value
* @param [String] hex : hex value
* @return [String] : complimentary color as hex value
*/
function hexToComplimentary(hex){
// Convert hex to rgb
// Credit to Denis http://stackoverflow.com/a/36253499/4939630
var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')';
// Get array of RGB values
rgb = rgb.replace(/[^\d,]/g, '').split(',');
var r = rgb[0], g = rgb[1], b = rgb[2];
// Convert RGB to HSL
// Adapted from answer by 0x000f http://stackoverflow.com/a/34946092/4939630
r /= 255.0;
g /= 255.0;
b /= 255.0;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, l = (max + min) / 2.0;
if(max == min) {
h = s = 0; //achromatic
} else {
var d = max - min;
s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));
if(max == r && g >= b) {
h = 1.0472 * (g - b) / d ;
} else if(max == r && g < b) {
h = 1.0472 * (g - b) / d + 6.2832;
} else if(max == g) {
h = 1.0472 * (b - r) / d + 2.0944;
} else if(max == b) {
h = 1.0472 * (r - g) / d + 4.1888;
}
}
h = h / 6.2832 * 360.0 + 0;
// Shift hue to opposite side of wheel and convert to [0-1] value
h+= 180;
if (h > 360) { h -= 360; }
h /= 360;
// Convert h s and l values into r g and b values
// Adapted from answer by Mohsen http://stackoverflow.com/a/9493060/4939630
if(s === 0){
r = g = b = l; // achromatic
} else {
var hue2rgb = function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
// Convert r b and g values to hex
rgb = b | (g << 8) | (r << 16);
return "#" + (0x1000000 | rgb).toString(16).substring(1);
}
Upvotes: 37