In order to check if two colors in grayscale will be too close to be distinguished by the human eye. I want to be able to generate a warning to the user if \'dangerous\' col
Just for a better syntax and ease of use I have put the whole theory into one single parser within an object that runs as follows.
The parser will compute these values in one step from color 318261:
A returned object will look like:
hex: "#318261"
rgb: {
r: 49,
g: 130,
b: 97
}
int: 10313648
dec: {
r: 0.19215686274509805,
g: 0.5098039215686274,
b: 0.3803921568627451
}
lin: {
r: 0.030713443732993635,
g: 0.2232279573168085,
b: 0.11953842798834562
}
y: 0.17481298771137443
lstar: 48.86083783595441
JavaScript can call the object internal parser with a Hex color string as a parameter. The hex string can look like either 000 or #000 or 000000 or #000000. There are two ways to process the result.
A: take the returned object as a whole into a variable:
var result = Color_Parser.parseHex('318261');
var lstar = result.lstar;
B: parse once and thereafter access parts of the last parser result. For example pick only the the L* contrast value would be just:
Color_Parser.parseHex('#ABC');
var lstar = Color_Parser.result.lstar;
Here is the full code:
const Color_Parser = {
version: '1.0.0.beta',
name: 'Color_Parser',
result: null, // the parser output
loging: true, // set to false to disable writing each step to console log
parseHex: function(_input) {
if (this.loging) {
console.log(this.name + ', input: ' + _input);
}
this.result = {};
// pre flight checks
if (!_input) {
this.result.error = true;
console.log(this.name + ', error');
return this.result;
}
// first convert shorthand Hex strings to full strings
this.result.hex = String(_input);
if (this.result.hex.length == 3) {
this.result.hex = '#' + this.result.hex.substr(0, 1) + this.result.hex.substr(0, 1) + this.result.hex.substr(1, 1) + this.result.hex.substr(1, 1) + this.result.hex.substr(2, 1) + this.result.hex.substr(2, 1);
}
if (this.result.hex.length == 4) {
this.result.hex = '#' + this.result.hex.substr(1, 1) + this.result.hex.substr(1, 1) + this.result.hex.substr(2, 1) + this.result.hex.substr(2, 1) + this.result.hex.substr(3, 1) + this.result.hex.substr(3, 1);
}
if (this.result.hex.length == 6) {
this.result.hex = '#' + this.result.hex;
}
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.hex);
}
// second get int values from the string segments as channels
this.result.rgb = {
r: null,
g: null,
b: null
};
this.result.rgb.r = parseInt(this.result.hex.substr(1, 2), 16);
this.result.rgb.g = parseInt(this.result.hex.substr(3, 2), 16);
this.result.rgb.b = parseInt(this.result.hex.substr(5, 2), 16);
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.rgb);
}
// third get the combined color int value
this.result.int = ((this.result.rgb.r & 0x0ff) << 16) | ((this.result.rgb.g & 0x0ff) << 8) | (this.result.rgb.b & 0x0ff);
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.int);
}
// fourth turn 8 bit channels to decimal
this.result.dec = {
r: null,
g: null,
b: null
};
this.result.dec.r = this.result.rgb.r / 255.0; // red channel to decimal
this.result.dec.g = this.result.rgb.g / 255.0; // green channel to decimal
this.result.dec.b = this.result.rgb.b / 255.0; // blue channel to decimal
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.dec);
}
// fifth linearize each channel
this.result.lin = {
r: null,
g: null,
b: null
};
for (var i = 0, len = 3; i < len; i++) {
if (this.result.dec[['r', 'g', 'b'][i]] <= 0.04045) {
this.result.lin[['r', 'g', 'b'][i]] = this.result.dec[['r', 'g', 'b'][i]] / 12.92;
} else {
this.result.lin[['r', 'g', 'b'][i]] = Math.pow(((this.result.dec[['r', 'g', 'b'][i]] + 0.055) / 1.055), 2.4);
}
}
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.lin);
}
// get Y from linear result
this.result.y = (0.2126 * (this.result.lin.r)); // red channel
this.result.y += (0.7152 * (this.result.lin.g)); // green channel
this.result.y += (0.0722 * (this.result.lin.b)); // blue channel
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.y);
}
// get L* contrast from Y
if (this.result.y <= (216 / 24389)) {
this.result.lstar = this.result.y * (24389 / 27);
} else {
this.result.lstar = Math.pow(this.result.y, (1 / 3)) * 116 - 16;
}
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.lstar);
}
// compute grayscale is to be continued hereafter
// compute inverted rgb color
this.result.invert = {
r: null,
g: null,
b: null,
hex: null
};
this.result.invert.r = (255 - this.result.rgb.r);
this.result.invert.g = (255 - this.result.rgb.g);
this.result.invert.b = (255 - this.result.rgb.b);
// reverse compute hex from inverted rgb
this.result.invert.hex = this.result.invert.b.toString(16); // begin with blue channel
if (this.result.invert.hex.length < 2) {
this.result.invert.hex = '0' + this.result.invert.hex;
}
this.result.invert.hex = this.result.invert.g.toString(16) + this.result.invert.hex;
if (this.result.invert.hex.length < 4) {
this.result.invert.hex = '0' + this.result.invert.hex;
}
this.result.invert.hex = this.result.invert.r.toString(16) + this.result.invert.hex;
if (this.result.invert.hex.length < 6) {
this.result.invert.hex = '0' + this.result.invert.hex;
}
this.result.invert.hex = '#' + this.result.invert.hex;
this.result.error = false;
if (this.loging) {
console.log(this.name + ', final output:');
}
if (this.loging) {
console.log(this.result);
}
return this.result;
}
}