Average 2 hex colors together in javascript - javascript

Alright thought I would throw this one out there for the crowd to think over.
Given a function (written in javascript) that expects two strings formated like a hex color (ex #FF0000)
return a hex color that is the average of both of the colors passed.
function averageColors(firstColor,secondColor)
{
...
return avgColor;
}
--edit--
average would be defined as
if the color passed was yellow and the second was light purple the returned color would be a medium orange

I hate sounding like the oh-so-broken jQuery record, but there is a jQuery plugin for this already.

A quick/dirty/convenient/ES6 way to blend two hex colors by a specified perecentage:
// blend two hex colors together by an amount
function blendColors(colorA, colorB, amount) {
const [rA, gA, bA] = colorA.match(/\w\w/g).map((c) => parseInt(c, 16));
const [rB, gB, bB] = colorB.match(/\w\w/g).map((c) => parseInt(c, 16));
const r = Math.round(rA + (rB - rA) * amount).toString(16).padStart(2, '0');
const g = Math.round(gA + (gB - gA) * amount).toString(16).padStart(2, '0');
const b = Math.round(bA + (bB - bA) * amount).toString(16).padStart(2, '0');
return '#' + r + g + b;
}
console.log(blendColors('#00FF66', '#443456', 0.5));
Where amount should be 0 to 1, with 0 being exactly colorA, 1 being exactly colorB, and 0.5 being the "midpoint".

Only requires a few lines of POJS if you don't want to bother with lots of unnecessary stuff:
// Expects input as 'nnnnnn' where each nn is a
// 2 character hex number for an RGB color value
// e.g. #3f33c6
// Returns the average as a hex number without leading #
var averageRGB = (function () {
// Keep helper stuff in closures
var reSegment = /[\da-z]{2}/gi;
// If speed matters, put these in for loop below
function dec2hex(v) {return v.toString(16);}
function hex2dec(v) {return parseInt(v,16);}
return function (c1, c2) {
// Split into parts
var b1 = c1.match(reSegment);
var b2 = c2.match(reSegment);
var t, c = [];
// Average each set of hex numbers going via dec
// always rounds down
for (var i=b1.length; i;) {
t = dec2hex( (hex2dec(b1[--i]) + hex2dec(b2[i])) >> 1 );
// Add leading zero if only one character
c[i] = t.length == 2? '' + t : '0' + t;
}
return c.join('');
}
}());

Smells like homework to me, but here's my clue.
Take each hex value for R, G, and B, and average each of them. If necessary convert to Decimal to do the math.
function d2h(d) {return d.toString(16).padStart(2,'0');}
function h2d(h) {return parseInt(h,16);}
Then return a string containing the concatenated values of the three elements.

Here's a compact set of relevant (interdependent) functions:
Hex ⟷ RGB Color Conversion:
function hexToRgb(h){return['0x'+h[1]+h[2]|0,'0x'+h[3]+h[4]|0,'0x'+h[5]+h[6]|0]}
function rgbToHex(r,g,b){return"#"+((1<<24)+(r<<16)+(g<<8)+ b).toString(16).slice(1);}
Calculate Average of 2 Hex Colors: Requires conversion functions (above)
function avgHex(h1,h2){a=hexToRgb(h1);b=hexToRgb(h2); return rgbToHex(~~((a[0]+b[0])/2),~~((a[1]+b[1])/2),~~((a[2]+b[2])/2));}
Generate Random Hex Color:
function rndHex(){return'#'+('00000'+(Math.random()*(1<<24)|0).toString(16)).slice(-6);}
Run snippet for demo:
// color functions (average/random/conversion)
function hexToRgb(h){return['0x'+h[1]+h[2]|0,'0x'+h[3]+h[4]|0,'0x'+h[5]+h[6]|0]}
function rgbToHex(r,g,b){return"#"+((1<<24)+(r<<16)+(g<<8)+ b).toString(16).slice(1);}
function rndHex(){return'#'+('00000'+(Math.random()*(1<<24)|0).toString(16)).slice(-6);}
function avgHex(h1,h2){a=hexToRgb(h1);b=hexToRgb(h2);return rgbToHex(~~((a[0]+b[0])/2),~~((a[1]+b[1])/2),~~((a[2]+b[2])/2));}
//code below is just for the demo
function auto(){if(chk.checked){tmr=setInterval(rnd,1000)}else{clearTimeout(tmr)}}auto();
function rnd(go){for(h of[h1,h2]){h.value=rndHex();}avgInput();}
addEventListener('input',avgInput);
function avgInput(){ // get avg & colorize
ha.value=avgHex(h1.value,h2.value);
for(h of [h1,h2,ha])h.style.background=h.value;
}
*{font-family:monospace;font-size:5vw; }
<label>Color 1 → <input id='h1'></label><br>
<label>Average → <input id='ha'></label><br>
<label>Color 2 → <input id='h2'></label><br>
<label>Type hex colors or <input type='checkbox' id='chk' onclick='auto()' style=' transform: scale(1.5)'checked>Autorandom</label>

Here is my function, hope it helps.
function averageColors( colorArray ){
var red = 0, green = 0, blue = 0;
for ( var i = 0; i < colorArray.length; i++ ){
red += hexToR( "" + colorArray[ i ] + "" );
green += hexToG( "" + colorArray[ i ] + "" );
blue += hexToB( "" + colorArray[ i ] + "" );
}
//Average RGB
red = (red/colorArray.length);
green = (green/colorArray.length);
blue = (blue/colorArray.length);
console.log(red + ", " + green + ", " + blue);
return new THREE.Color( "rgb("+ red +","+ green +","+ blue +")" );
}
//get the red of RGB from a hex value
function hexToR(h) {return parseInt((cutHex( h )).substring( 0, 2 ), 16 )}
//get the green of RGB from a hex value
function hexToG(h) {return parseInt((cutHex( h )).substring( 2, 4 ), 16 )}
//get the blue of RGB from a hex value
function hexToB(h) {return parseInt((cutHex( h )).substring( 4, 6 ), 16 )}
//cut the hex into pieces
function cutHex(h) {if(h.charAt(1) == "x"){return h.substring( 2, 8 );} else {return h.substring(1,7);}}

Very late to this party, but I was personally looking for a way to average an undefined amount of HEX values. Based on the answer #RobG, I came up with this. Granted, the more colors you add the more brown/greyish they get, but, perhaps it helps!
/**
* Averages an array of hex colors. Returns one hex value (with leading #)
*
* #param {Array} colors - An array of hex strings, e.g. ["#001122", "#001133", ...]
*/
function averageHex(colors) {
// transform all hex codes to integer arrays, e.g. [[R, G, B], [R,G,B], ...]
let numbers = colors.map(function(hex) {
// split in seperate R, G and B
let split = hex.match(/[\da-z]{2}/gi);
// transform to integer values
return split.map(function(toInt) {
return parseInt(toInt, 16);
});
});
// reduce the array by averaging all values, resulting in an average [R, G, B]
let averages = numbers.reduce(function(total, amount, index, array) {
return total.map(function(subtotal, subindex) {
// if we reached the last color, average it out and return the hex value
if (index == array.length - 1) {
let result = Math.round((subtotal + amount[subindex]) / array.length).toString(16);
// add a leading 0 if it is only one character
return result.length == 2 ? '' + result : '0' + result;
} else {
return subtotal + amount[subindex];
}
});
});
// return them as a single hex string
return "#" + averages.join('');
}
console.log(averageHex(["#FF110C", "#0000AA", "#55063d", "#06551e"]));
// expected: #571b44, see also https://www.colorhexa.com/ and enter "#FF110C+#0000AA+#55063d+#06551e"

Here is the function
function avgColor(color1, color2) {
//separate each color alone (red, green, blue) from the first parameter (color1)
//then convert to decimal
let color1Decimal = {
red: parseInt(color1.slice(0, 2), 16),
green: parseInt(color1.slice(2, 4), 16),
blue: parseInt(color1.slice(4, 6), 16)
}
//separate each color alone (red, green, blue) from the second parameter (color2)
//then convert to decimal
let color2Decimal = {
red: parseInt(color2.slice(0, 2), 16),
green: parseInt(color2.slice(2, 4), 16),
blue: parseInt(color2.slice(4, 6), 16),
}
// calculate the average of each color (red, green, blue) from each parameter (color1,color2)
let color3Decimal = {
red: Math.ceil((color1Decimal.red + color2Decimal.red) / 2),
green: Math.ceil((color1Decimal.green + color2Decimal.green) / 2),
blue: Math.ceil((color1Decimal.blue + color2Decimal.blue) / 2)
}
//convert the result to hexadecimal and don't forget if the result is one character
//then convert it to uppercase
let color3Hex = {
red: color3Decimal.red.toString(16).padStart(2, '0').toUpperCase(),
green: color3Decimal.green.toString(16).padStart(2, '0').toUpperCase(),
blue: color3Decimal.blue.toString(16).padStart(2, '0').toUpperCase()
}
//put the colors (red, green, blue) together to have the output
let color3 = color3Hex.red + color3Hex.green + color3Hex.blue
return color3
}
console.log(avgColor("FF33CC", "3300FF"))
// avgColor("FF33CC", "3300FF") => "991AE6"
console.log(avgColor("991AE6", "FF0000"))
// avgColor("991AE6", "FF0000") => "CC0D73"
console.log(avgColor("CC0D73", "0000FF"))
// avgColor("CC0D73", "0000FF") => "6607B9"
To check you can use this link and midpoint 1 then blend
https://meyerweb.com/eric/tools/color-blend/#CC0D73:0000FF:1:hex

Related

Create a function that auto detects the color format, rgb or hex, and converts it to hex or rgb respectively

The question is:
Write a function that converts HEX to RGB. Then Make that function auto-dect the formats so that if you enter HEX color format it returns RGB and if you enter RGB color format it returns HEX.
I wrote this:
//Function to convert hex to rgb
const hexTorgb = (hex) => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
// return {r, g, b}
return { r, g, b };
}
console.log(hexTorgb("#ff33ff"));
/*
{
"r": 255,
"g": 51,
"b": 255
}
*/
//Functions to convert rgb to hex
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
But, I am missing something in the function to auto-detecte the format. Right now, I got this, but I'm getting an error when I try to use this next function to detect the format
function detect(input) {
if (input.includes('#')) {
return console.log(hexTorgb(input))
} else if (input.includes(',')){
console.log(rgbToHex(input))
}
}
When I wrote in RGB format, I get an error
detect(0, 0, 0)
that's good, but I thought it was more a question of doing a more "literal" conversion, using standard textual syntax, like in CSS.
const
x2n = x => parseInt(x,16) // convert hexa value to number
, n2x = n => `0${n.toString(16)}`.slice(-2).toUpperCase() // convert n to 2digits Hexa
, rgb2hex = rgb => rgb.match(/\d+/g).reduce((s,x)=>s+n2x(Number(x)),'#')
, hex2rgb = hexa => 'rgb('+hexa.match(/([\da-f]{2})/gi).map(x2n).join(',')+')'
;
function reverse_RGB_Hexa(input) // need to be improved by a regex control
{
if (input.includes('#')) return hex2rgb(input);
else if (input.includes(',')) return rgb2hex(input);
else return 'unknown input value';
}
console.log( reverse_RGB_Hexa("#1A0BFF")); // rgb(26,11,255)
console.log( reverse_RGB_Hexa('rgb(26,11,255)')); // #1A0BFF
console.log( reverse_RGB_Hexa('fdgkjl') ) // unknown
After correcting from one input to three inputs, I still had to make some adjustments like setting variables to r, g, b e hex, to assume the value of the inputs
//Function to convert hex to rgb
const hexTorgb = (hex) => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
// return {r, g, b}
return { r, g, b };
}
//Functions to convert rgb to hex
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
function convert(input1, input2, input3) {
if (input2===undefined) {
var hex = input1
console.log(hexTorgb(hex))
} else {
var r = input1
var g = input2
var b = input3
console.log(rgbToHex(r,g,b))
}}
convert('#ff33ff')
convert('0','0','0')

Converting RGB to HEX fails

Following this question and many others, I'm trying to convert an rgb value to a hex value.
Copying/pasting the most used and accepted answer, I've made this script
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(rgb) {
var colors = rgb.split("(")
colors = colors[1].split(")")
colors = colors[0].split(",")
var r = 255 - parseInt(colors[0])
var g = 255 - parseInt(colors[1])
var b = 255 - parseInt(colors[2])
return componentToHex(r) + componentToHex(g) + componentToHex(b);
}
alert(rgbToHex("rgb(0, 51, 255)"))
Result:
ffcc00
Expected result:
0033ff
Why is it not working?
Use the parseInt result directly, not 255 minus the value:
var r = parseInt(colors[0])
var g = parseInt(colors[1])
var b = parseInt(colors[2])
You're currently producing the exact opposite color by outputting values complementary to (rather than equivalent to) the input values.
Instead of 255 - parseInt(colors[i]), it should be parseInt(colors[i]).
In your current implementation - if red is 0, 255 - 0 = 255, which in hex is FF.

Using Native JavaScript to Desaturate a Colour

I have a colour picker where the user can specify a hex colour.
I also want a saturation slider where the user can adjust the saturation, and get the new hex colour as an output.
Is there a way I can, in JavaScript, convert a saturation value, and a hex colour, into a new hex colour?
So, say for example I have a value #FF0000 and a saturation of 50 (out of 100) how would I ascertain the new hex colour from this?
I can't use any libraries for it because I'm creating it as a plugin for my website, and I'm trying to keep it as light as possible.
http://jsfiddle.net/5sfDQ/
$("#color, #saturation").change(function(){
updateColor();
});
function updateColor(){
var col = hexToRgb($("#color").val());
var sat = Number($('#saturation').val())/100;
var gray = col.r * 0.3086 + col.g * 0.6094 + col.b * 0.0820;
col.r = Math.round(col.r * sat + gray * (1-sat));
col.g = Math.round(col.g * sat + gray * (1-sat));
col.b = Math.round(col.b * sat + gray * (1-sat));
var out = rgbToHex(col.r,col.g,col.b);
$('#output').val(out);
$('body').css("background",out);
}
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
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 applySat(sat, hex) {
var hash = hex.substring(0, 1) === "#";
hex = (hash ? hex.substring(1) : hex).split("");
var long = hex.length > 3,
rgb = [],
i = 0,
len = 3;
rgb.push( hex.shift() + (long ? hex.shift() : "") );
rgb.push( hex.shift() + (long ? hex.shift() : "") );
rgb.push( hex.shift() + (long ? hex.shift() : "") );
for( ; i < len; i++ ) {
if ( !long ) {
rgb[i] += rgb[i];
}
rgb[i] = Math.round( parseInt(rgb[i], 16)/100*sat).toString(16);
rgb[i] += rgb[i].length === 1 ? rgb[i] : "";
}
return (hash ? "#" : "") + rgb.join("");
}
console.log(applySat(50, "#ff0000")); // "#7f0000";
console.log(applySat(50, "ff0000")); // "7f0000";
console.log(applySat(50, "#fed")); // "#7f776f"
console.log(applySat(50, "fed")); // "7f776f"
console.log(applySat(20, "#addfaa")); // "#232d22"
If you really don't want to use a library, see mjijackson's RGB to HSL conversion page.
Copy the code to do RGB hex to HSL (or HSV) conversions. As the slider is moved, you'll need to use these to convert between the color models to get the saturation value, modify it, and then get the resulting rgb color back.
Note: HSL and HSV are standard color models. A few of the other answers are proposing definitions of "saturation" that do not correspond to these standard color models. Users will be confused as the alternate definitions will not give results consistent with what they'd expect from GIMP, Photoshop or other common graphics applications.
Real simple and easy function, input your color, and add how much you want to desaturate it by.
it does a pretty good job, but its not perfectly accurate.
function addSaturation(color, amount){
var color = color.replace('#', '').split('');
var letters = '0123456789ABCDEF'.split('');
for(var i = 0; i < color.length; i++){
var newSaturation = 0;
if(letters.indexOf(color[i]) + amount > 15) newSaturation = 15;
else if(letters.indexOf(color[i]) + amount < 0) newSaturation = 0;
else newSaturation = letters.indexOf(color[i]) + amount;
color[i] = letters[newSaturation];
}
return "#" + color.join('');
}
you can use positive or negative amounts as well.
You could utilize the Javascript provided in this solution to match your needs
To change the saturation of an element, shift each of the three HEX values simultaneously, bringing the values closer to 128 (half of 256).
background-color: #FF0000; // rgb(255, 0, 0)
to this...
background-color: #BF4040; // rgb(191, 64, 64)

Working with hex strings and hex values more easily in Javascript

I have some code which takes strings representing hexadecimal numbers - hex colors, actually - and adds them. For example, adding aaaaaa and 010101 gives the output ababab.
However, my method seems unnecessarily long and complicated:
var hexValue = "aaaaaa";
hexValue = "0x" + hexValue;
hexValue = parseInt(hexValue, 16);
hexValue = hexValue + 0x010101;
hexValue = hexValue.toString(16);
document.write(hexValue); // outputs 'ababab'
The hex value is still a string after concatenating 0x, so then I have to change it to a number, then I can add, and then I have to change it back into hex format! There are even more steps if the number I'm adding to it is a hexadecimal string to begin with, or if you take into consideration that I am removing the # from the hex color before all this starts.
Surely there's a simpler way to do such simple hexadecimal calculations! And just to be clear, I don't mean just putting it all on one line like (parseInt("0x"+"aaaaaa",16)+0x010101).toString(16) or using shorthand - I mean actually doing less operations.
Is there some way to get javascript to stop using decimal for all of its mathematical operations and use hex instead? Or is there some other method of making JS work with hex more easily?
No, there is no way to tell the JavaScript language to use hex integer format instead of decimal by default. Your code is about as concise as it gets but note that you do not need to prepend the "0x" base indicator when you use "parseInt" with a base.
Here is how I would approach your problem:
function addHexColor(c1, c2) {
var hexStr = (parseInt(c1, 16) + parseInt(c2, 16)).toString(16);
while (hexStr.length < 6) { hexStr = '0' + hexStr; } // Zero pad.
return hexStr;
}
addHexColor('aaaaaa', '010101'); // => 'ababab'
addHexColor('010101', '010101'); // => '020202'
As mentioned by a commenter, the above solution is chock full of problems, so below is a function that does proper input validation and adds color channels separately while checking for overflow.
function addHexColor2(c1, c2) {
const octetsRegex = /^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i
const m1 = c1.match(octetsRegex)
const m2 = c2.match(octetsRegex)
if (!m1 || !m2) {
throw new Error(`invalid hex color triplet(s): ${c1} / ${c2}`)
}
return [1, 2, 3].map(i => {
const sum = parseInt(m1[i], 16) + parseInt(m2[i], 16)
if (sum > 0xff) {
throw new Error(`octet ${i} overflow: ${m1[i]}+${m2[i]}=${sum.toString(16)}`)
}
return sum.toString(16).padStart(2, '0')
}).join('')
}
addHexColor2('aaaaaa', 'bogus!') // => Error: invalid hex color triplet(s): aaaaaa / bogus!
addHexColor2('aaaaaa', '606060') // => Error: octet 1 overflow: aa+60=10a
How about this:
var hexValue = "aaaaaa";
hexValue = (parseInt(hexValue, 16) + 0x010101).toString(16);
document.writeln(hexValue); // outputs 'ababab'
There is no need to add the 0x prefix if you use parseInt.
I think accepted answer is wrong. Hexadecimal color representation is not a linear. But instead, 3 sets of two characters are given to R, G & B.
So you can't just add a whole number and expect to RGB to add up correctly.
For Example
n1 = '005500'; <--- green
n2 = '00ff00'; <--- brighter green
Adding these numbers should result in a greener green.
In no way, adding greens should increase RED to increase. but by doing what accepted answer is doing, as in just treat whole number as one number then you'd carry over for numbers adding upto greater than f, f+1 = 10.
you get `015400` so by adding greens the RED increased .... WRONG
adding 005500 + 00ff00 should result in, = 00ff00. You can't add more green to max green.
For folks looking for a function that can add and subtract HEX colors without going out of bounds on an individual tuple, I wrote this function a few minutes ago to do just that:
export function shiftColor(base, change, direction) {
const colorRegEx = /^\#?[A-Fa-f0-9]{6}$/;
// Missing parameter(s)
if (!base || !change) {
return '000000';
}
// Invalid parameter(s)
if (!base.match(colorRegEx) || !change.match(colorRegEx)) {
return '000000';
}
// Remove any '#'s
base = base.replace(/\#/g, '');
change = change.replace(/\#/g, '');
// Build new color
let newColor = '';
for (let i = 0; i < 3; i++) {
const basePiece = parseInt(base.substring(i * 2, i * 2 + 2), 16);
const changePiece = parseInt(change.substring(i * 2, i * 2 + 2), 16);
let newPiece = '';
if (direction === 'add') {
newPiece = (basePiece + changePiece);
newPiece = newPiece > 255 ? 255 : newPiece;
}
if (direction === 'sub') {
newPiece = (basePiece - changePiece);
newPiece = newPiece < 0 ? 0 : newPiece;
}
newPiece = newPiece.toString(16);
newPiece = newPiece.length < 2 ? '0' + newPiece : newPiece;
newColor += newPiece;
}
return newColor;
}
You pass your base color as parameter 1, your change as parameter 2, and then 'add' or 'sub' as the last parameter depending on your intent.

How to subtract a color from another

Is it possible to subtract a color from another one ?
Example (correct me if i'm wrong):
If i subtract red and green from white, i am expecting the
result to be blue.
var white = 0xFFFFFF,
red = 0xFF0000,
result = white - red;
console.log(result); //65535 <-- what is that ? can it be converted to 0x00FFFF ?
[update]
Thanks to Rocket's answer, it turned out i needed a function() to convert my results into an actual color.
Here is the final working example:
var toColor = function ( d ) {
var c = Number(d).toString(16);
return "#" + ( "000000".substr( 0, 6 - c.length ) + c.toUpperCase() );
},
white = 0xFFFFFF,
red = 0xFF0000,
green = 0x00FF00,
result = toColor( white - red - green );
console.log( result ); // logs the expected result: "#0000FF"
Your white-red works fine, it's just that JavaScript represents the values as base 10 values. You need to convert them back to base 16 (hex). Check out this answer, to convert the value back to hex.
var white = 0xFFFFFF, // Stored as 16777215
red = 0xFF0000, // Stored as 16711680
result = white - red; // 16777215 - 16711680 = 65535
console.log(result); // It's stored as base 10, so it prints 65535
var resultHex = result.toString(16); // 'ffff', converted back to hex
I'm just assuming you are using RGB, because there are plenty of other ways to mix colors. You have to represent a color into 3* different parts, R G and B.
// R G B
var white = [ 1, 1, 1];
var red = [ 0, 0, 0 ];
var result = [ white[0] - red[0], white[1] - red[1], white[2] - red[2] ;
To substract the color you have to substract each component of the color, and perhaps convert it back to hex later on.
* You might want to add Alpha later on

Categories

Resources