Kalamar Obliwy
Kalamar Obliwy

Reputation: 861

Calculate color HEX having 2 colors and percent/position

Is it possible to calculate a color in a middle of a gradient?

var color1 = 'FF0000';
var color2 = '00FF00';

// 50% between the two colors, should return '808000'
var middle = gradient(color1, color2, 0.5); 

I only have two hex strings, and I want one in return.

Upvotes: 26

Views: 19437

Answers (4)

Nilz11
Nilz11

Reputation: 1242

I found out that in the Javascript substring method the to parameter index is not included in the returned string. That means:

var string = "test";
//index:      0123
alert(string.substring(1,3));

//will alert es and NOT est

So it should be:

parseInt(color1.substring(0,2), 16);
parseInt(color1.substring(2,4), 16);

and

parseInt(color1.substring(4,6), 16);

Upvotes: 4

jedwards
jedwards

Reputation: 30200

An ES6 version with comprehensions:

function interpolateColor(c0, c1, f){
    c0 = c0.match(/.{1,2}/g).map((oct)=>parseInt(oct, 16) * (1-f))
    c1 = c1.match(/.{1,2}/g).map((oct)=>parseInt(oct, 16) * f)
    let ci = [0,1,2].map(i => Math.min(Math.round(c0[i]+c1[i]), 255))
    return ci.reduce((a,v) => ((a << 8) + v), 0).toString(16).padStart(6, "0")
}

As in the accepted answer, c0,c1 are color codes (without the leading #) and f is "progress" between the two values. (At f=0 this ends up returning c0, at f=1 this returns c1).

  • The first two lines convert the color codes into arrays of scaled integers
  • The third line:
    • "zips" the two integer arrays
    • sums the corresponding values
    • rounds the sum and clamps it to 0-255
  • The fourth line:
    • converts the integer array into a single integer (reduce and bitshifting)
    • converts the integer into its hexadecimal string form
    • ensures the resulting string is 6 characters long and returns it

Upvotes: 7

Masih Jahangiri
Masih Jahangiri

Reputation: 10897

You can use this ready function (ES6):

const calculateMiddleColor = ({
  color1 = 'FF0000',
  color2 = '00FF00',
  ratio,
}) => {
  const hex = (color) => {
    const colorString = color.toString(16);
    return colorString.length === 1 ? `0${colorString}` : colorString;
  };

  const r = Math.ceil(
    parseInt(color2.substring(0, 2), 16) * ratio
      + parseInt(color1.substring(0, 2), 16) * (1 - ratio),
  );
  const g = Math.ceil(
    parseInt(color2.substring(2, 4), 16) * ratio
      + parseInt(color1.substring(2, 4), 16) * (1 - ratio),
  );
  const b = Math.ceil(
    parseInt(color2.substring(4, 6), 16) * ratio
      + parseInt(color1.substring(4, 6), 16) * (1 - ratio),
  );

  return hex(r) + hex(g) + hex(b);
};
//////////////////////////////////////////////////////////////////////
console.log(calculateMiddleColor({ ratio: 0 / 5 })); // ff0000
console.log(calculateMiddleColor({ ratio: 5 / 5 })); // 00ff00
console.log(calculateMiddleColor({ ratio: 2.5 / 5 })); // 808000
console.log(calculateMiddleColor({ ratio: 4.2 / 5 })); // 29d700

Upvotes: 3

techfoobar
techfoobar

Reputation: 66663

This should work:

It basically involves converting them to decimal, finding the halves, converting the results back to hex and then concatenating them.

var color1 = 'FF0000';
var color2 = '00FF00';
var ratio = 0.5;
var hex = function(x) {
    x = x.toString(16);
    return (x.length == 1) ? '0' + x : x;
};

var r = Math.ceil(parseInt(color1.substring(0,2), 16) * ratio + parseInt(color2.substring(0,2), 16) * (1-ratio));
var g = Math.ceil(parseInt(color1.substring(2,4), 16) * ratio + parseInt(color2.substring(2,4), 16) * (1-ratio));
var b = Math.ceil(parseInt(color1.substring(4,6), 16) * ratio + parseInt(color2.substring(4,6), 16) * (1-ratio));

var middle = hex(r) + hex(g) + hex(b);

Upvotes: 50

Related Questions