Average 2 hex colors together in javascript

Viewed 11701

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 enter image description here

if the color passed was yellow and the second was light purple the returned color would be a medium orange

8 Answers

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".

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>

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