@hexorialstudio/color-blinder
Version:
Color Blinder Simulate color blindness of the given valid color hex code
227 lines (221 loc) • 6.34 kB
JavaScript
/*
* color-blinder
* https://github.com/HexorialStudio/color-blinder
*
* This source was copied from http://mudcu.be/sphere/js/Color.Blind.js
*
* It contains modifications for use anywhere.
*
* The original copyright is included below.
*
* Copyright (c) 2020 Hexorial Studio LLP
* Licensed under the MIT license.
*/
/*
* The Color Blindness Simulation function is
* copyright (c) 2000-2001 by Matthew Wickline and the
* Human-Computer Interaction Resource Network ( http://hcirn.com/ ).
*
* It is used with the permission of Matthew Wickline and HCIRN,
* and is freely available for non-commercial use. For commercial use, please
* contact the Human-Computer Interaction Resource Network ( http://hcirn.com/ ).
* ------------------------
* blind.protan =
* cpu = 0.735; // confusion point, u coord
* cpv = 0.265; // confusion point, v coord
* abu = 0.115807; // color axis begining point (473nm), u coord
* abv = 0.073581; // color axis begining point (473nm), v coord
* aeu = 0.471899; // color axis ending point (574nm), u coord
* aev = 0.527051; // color axis ending point (574nm), v coord
* blind.deutan =
* cpu = 1.14; // confusion point, u coord
* cpv = -0.14; // confusion point, v coord
* abu = 0.102776; // color axis begining point (477nm), u coord
* abv = 0.102864; // color axis begining point (477nm), v coord
* aeu = 0.505845; // color axis ending point (579nm), u coord
* aev = 0.493211; // color axis ending point (579nm), v coord
* blind.tritan =
* cpu = 0.171; // confusion point, u coord
* cpv = -0.003; // confusion point, v coord
* abu = 0.045391; // color axis begining point (490nm), u coord
* abv = 0.294976; // color axis begining point (490nm), v coord
* aeu = 0.665764; // color axis ending point (610nm), u coord
* aev = 0.334011; // color axis ending point (610nm), v coord
*
* m = (aev - abv) / (aeu - abu); // slope of color axis
* yi = blind[t].abv - blind[t].abu * blind[t].m; // "y-intercept" of axis (on the "v" axis at u=0)
*/
;
var colorProfile = "sRGB";
var gammaCorrection = 2.2;
var matrixXyzRgb = [
3.240712470389558,
-0.969259258688888,
0.05563600315398933,
-1.5372626602963142,
1.875996969313966,
-0.2039948802843549,
-0.49857440415943116,
0.041556132211625726,
1.0570636917433989,
];
var matrixRgbXyz = [
0.41242371206635076,
0.21265606784927693,
0.019331987577444885,
0.3575793401363035,
0.715157818248362,
0.11919267420354762,
0.1804662232369621,
0.0721864539171564,
0.9504491124870351,
];
// xy: coordinates, m: slope, yi: y-intercept
var blinder = {
protan: {
x: 0.7465,
y: 0.2535,
m: 1.273463,
yi: -0.073894,
},
deutan: {
x: 1.4,
y: -0.4,
m: 0.968437,
yi: 0.003331,
},
tritan: {
x: 0.1748,
y: 0,
m: 0.062921,
yi: 0.292119,
},
custom: {
x: 0.735,
y: 0.265,
m: -1.059259,
yi: 1.026914,
},
};
var convertRgbToXyz = function (o) {
var M = matrixRgbXyz;
var z = {};
var R = o.R / 255;
var G = o.G / 255;
var B = o.B / 255;
if (colorProfile === "sRGB") {
R = R > 0.04045 ? Math.pow((R + 0.055) / 1.055, 2.4) : R / 12.92;
G = G > 0.04045 ? Math.pow((G + 0.055) / 1.055, 2.4) : G / 12.92;
B = B > 0.04045 ? Math.pow((B + 0.055) / 1.055, 2.4) : B / 12.92;
} else {
R = Math.pow(R, gammaCorrection);
G = Math.pow(G, gammaCorrection);
B = Math.pow(B, gammaCorrection);
}
z.X = R * M[0] + G * M[3] + B * M[6];
z.Y = R * M[1] + G * M[4] + B * M[7];
z.Z = R * M[2] + G * M[5] + B * M[8];
return z;
};
var convertXyzToXyy = function (o) {
var n = o.X + o.Y + o.Z;
if (n === 0) {
return { x: 0, y: 0, Y: o.Y };
}
return { x: o.X / n, y: o.Y / n, Y: o.Y };
};
exports.Blind = function (rgb, type, anomalize) {
var z,
v,
n,
line,
c,
slope,
yi,
dx,
dy,
dX,
dY,
dZ,
dR,
dG,
dB,
_r,
_g,
_b,
ngx,
ngz,
M,
adjust;
if (type === "achroma") {
// D65 in sRGB
z = rgb.R * 0.212656 + rgb.G * 0.715158 + rgb.B * 0.072186;
z = { R: z, G: z, B: z };
if (anomalize) {
v = 1.75;
n = v + 1;
z.R = (v * z.R + rgb.R) / n;
z.G = (v * z.G + rgb.G) / n;
z.B = (v * z.B + rgb.B) / n;
}
return z;
}
line = blinder[type];
c = convertXyzToXyy(convertRgbToXyz(rgb));
// The confusion line is between the source color and the confusion point
slope = (c.y - line.y) / (c.x - line.x);
yi = c.y - c.x * slope; // slope, and y-intercept (at x=0)
// Find the change in the x and y dimensions (no Y change)
dx = (line.yi - yi) / (slope - line.m);
dy = slope * dx + yi;
dY = 0;
// Find the simulated colors XYZ coords
z = {};
z.X = (dx * c.Y) / dy;
z.Y = c.Y;
z.Z = ((1 - (dx + dy)) * c.Y) / dy;
// Calculate difference between sim color and neutral color
ngx = (0.312713 * c.Y) / 0.329016; // find neutral grey using D65 white-point
ngz = (0.358271 * c.Y) / 0.329016;
dX = ngx - z.X;
dZ = ngz - z.Z;
// find out how much to shift sim color toward neutral to fit in RGB space
M = matrixXyzRgb;
dR = dX * M[0] + dY * M[3] + dZ * M[6]; // convert d to linear RGB
dG = dX * M[1] + dY * M[4] + dZ * M[7];
dB = dX * M[2] + dY * M[5] + dZ * M[8];
z.R = z.X * M[0] + z.Y * M[3] + z.Z * M[6]; // convert z to linear RGB
z.G = z.X * M[1] + z.Y * M[4] + z.Z * M[7];
z.B = z.X * M[2] + z.Y * M[5] + z.Z * M[8];
_r = ((z.R < 0 ? 0 : 1) - z.R) / dR;
_g = ((z.G < 0 ? 0 : 1) - z.G) / dG;
_b = ((z.B < 0 ? 0 : 1) - z.B) / dB;
_r = _r > 1 || _r < 0 ? 0 : _r;
_g = _g > 1 || _g < 0 ? 0 : _g;
_b = _b > 1 || _b < 0 ? 0 : _b;
adjust = _r > _g ? _r : _g;
if (_b > adjust) {
adjust = _b;
}
// shift proportionally...
z.R += adjust * dR;
z.G += adjust * dG;
z.B += adjust * dB;
// apply gamma and clamp simulated color...
z.R =
255 * (z.R <= 0 ? 0 : z.R >= 1 ? 1 : Math.pow(z.R, 1 / gammaCorrection));
z.G =
255 * (z.G <= 0 ? 0 : z.G >= 1 ? 1 : Math.pow(z.G, 1 / gammaCorrection));
z.B =
255 * (z.B <= 0 ? 0 : z.B >= 1 ? 1 : Math.pow(z.B, 1 / gammaCorrection));
//
if (anomalize) {
v = 1.75;
n = v + 1;
z.R = (v * z.R + rgb.R) / n;
z.G = (v * z.G + rgb.G) / n;
z.B = (v * z.B + rgb.B) / n;
}
//
return z;
};