-
-
Save Lokno/df7c3bfdc9ad32558bb7 to your computer and use it in GitHub Desktop.
// JSON of 3x3 matrices which transform RGB colors into colorspace which | |
// simulate the imparement of various color blindness deficiency. | |
// | |
// Used by Coblis: http://www.color-blindness.com/Coblis-color-blindness-simulator/ | |
// | |
// The original website posting the matrices has been taken down: | |
// http://www.colorjack.com/labs/colormatrix/ | |
// | |
// RGB transform matrices generated by Michael of www.colorjack.com | |
// Which were created using code by Matthew Wickline and the | |
// Human-Computer Interaction Resource Network ( http://hcirn.com/ ) | |
// | |
// The original matrices were 5x5, for full homogeneous coordinates of RGBA | |
// They have been similified here to 3x3 matrices, because the additional | |
// dimensions were simple identity. | |
// | |
// These are very inaccurate so consider other methods. See comments on this gist. | |
var colorMats = {'Normal':[1,0,0, | |
0,1,0, | |
0,0,1], | |
// Red-Blind | |
'Protanopia': [0.567,0.433,0.000, | |
0.558,0.442,0.000, | |
0.000,0.242,0.758], | |
// Red-Weak | |
'Protanomaly': [0.817,0.183,0.000, | |
0.333,0.667,0.000, | |
0.000,0.125,0.875], | |
// Green-Blind | |
'Deuteranopia': [0.625,0.375,0.000, | |
0.700,0.300,0.000, | |
0.000,0.300,0.700], | |
// Green-Weak | |
'Deuteranomaly':[0.800,0.200,0.000, | |
0.258,0.742,0.000, | |
0.000,0.142,0.858], | |
// Blue-Blind | |
'Tritanopia': [0.950,0.050,0.000, | |
0.000,0.433,0.567, | |
0.000,0.475,0.525], | |
// Blue-Weak | |
'Tritanomaly': [0.967,0.033,0.00, | |
0.00,0.733,0.267, | |
0.00,0.183,0.817], | |
// Monochromacy | |
'Achromatopsia':[0.299,0.587,0.114, | |
0.299,0.587,0.114, | |
0.299,0.587,0.114], | |
// Blue Cone Monochromacy | |
'Achromatomaly':[0.618,0.320,0.062, | |
0.163,0.775,0.062, | |
0.163,0.320,0.516]}; |
Thanks to much for these! It is surprisingly hard to find the simple RGB-in to RGB-out transforms instead of all the stages to transform RGB into LMS space, etc. The simple transforms make it much easier to include code that helps folks with normal vision accommodate the colorblind.
Thanks to much for these! It is surprisingly hard to find the simple RGB-in to RGB-out transforms instead of all the stages to transform RGB into LMS space, etc. The simple transforms make it much easier to include code that helps folks with normal vision accommodate the colorblind.
I'm glad you've found them useful. It can be a useful tool to look at images of this nature. I also developed an experimental color picker in JS that might interest you, which lets you interact the colors in the XYZ space and see what colors are confused by various types of color blindness. https://www.rabbitfury.com/colorpicker/
Coblis runs on client side: https://www.color-blindness.com/coblis-color-blindness-simulator/
JS file: https://www.color-blindness.com/coblis2/js/coblis-compressed.js
Make the JS human readable with some converter.
You will see that the simulation process is a mess (part of the code):
function blindMK(a, b) {
var d = .312713,
e = .329016,
f = .358271,
g = a[2],
h = a[1],
i = a[0],
j = powGammaLookup[i],
k = powGammaLookup[h],
l = powGammaLookup[g],
m = .430574 * j + .34155 * k + .178325 * l,
n = .222015 * j + .706655 * k + .07133 * l,
o = .020183 * j + .129553 * k + .93918 * l,
p = m + n + o,
q = 0,
r = 0;
0 != p && (q = m / p, r = n / p);
var u, s = d * n / e,
t = f * n / e,
v = 0;
u = q < rBlind[b].cpu ? (rBlind[b].cpv - r) / (rBlind[b].cpu - q) : (r - rBlind[b].cpv) / (q - rBlind[b].cpu);
var w = r - q * u,
x = (rBlind[b].ayi - w) / (u - rBlind[b].am),
y = u * x + w,
z = x * n / y,
A = n,
B = (1 - (x + y)) * n / y,
C = 3.063218 * z - 1.393325 * A - .475802 * B,
D = -.969243 * z + 1.875966 * A + .041555 * B,
E = .067871 * z - .228834 * A + 1.069251 * B,
F = s - z,
G = t - B;
dr = 3.063218 * F - 1.393325 * v - .475802 * G, dg = -.969243 * F + 1.875966 * v + .041555 * G, db = .067871 * F - .228834 * v + 1.069251 * G;
var H = dr ? ((C < 0 ? 0 : 1) - C) / dr : 0,
I = dg ? ((D < 0 ? 0 : 1) - D) / dg : 0,
J = db ? ((E < 0 ? 0 : 1) - E) / db : 0,
K = Math.max(H > 1 || H < 0 ? 0 : H, I > 1 || I < 0 ? 0 : I, J > 1 || J < 0 ? 0 : J);
return C += K * dr, D += K * dg, E += K * db, [inversePow(C), inversePow(D), inversePow(E)]
}
function inversePow(a) {
return 255 * (a <= 0 ? 0 : a >= 1 ? 1 : Math.pow(a, 1 / 2.2))
}
function anomylize(a, b) {
var c = 1.75,
d = 1 * c + 1;
return [(c * b[0] + 1 * a[0]) / d, (c * b[1] + 1 * a[1]) / d, (c * b[2] + 1 * a[2]) / d]
}
function monochrome(a) {
var b = Math.round(.299 * a[0] + .587 * a[1] + .114 * a[2]);
return [b, b, b]
}
var ColorMatrixMatrixes = {
Normal: {
R: [100, 0, 0],
G: [0, 100, 0],
B: [0, 0, 100]
},
Protanopia: {
R: [56.667, 43.333, 0],
G: [55.833, 44.167, 0],
B: [0, 24.167, 75.833]
},
Protanomaly: {
R: [81.667, 18.333, 0],
G: [33.333, 66.667, 0],
B: [0, 12.5, 87.5]
},
Deuteranopia: {
R: [62.5, 37.5, 0],
G: [70, 30, 0],
B: [0, 30, 70]
},
Deuteranomaly: {
R: [80, 20, 0],
G: [25.833, 74.167, 0],
B: [0, 14.167, 85.833]
},
Tritanopia: {
R: [95, 5, 0],
G: [0, 43.333, 56.667],
B: [0, 47.5, 52.5]
},
Tritanomaly: {
R: [96.667, 3.333, 0],
G: [0, 73.333, 26.667],
B: [0, 18.333, 81.667]
},
Achromatopsia: {
R: [29.9, 58.7, 11.4],
G: [29.9, 58.7, 11.4],
B: [29.9, 58.7, 11.4]
},
Achromatomaly: {
R: [61.8, 32, 6.2],
G: [16.3, 77.5, 6.2],
B: [16.3, 32, 51.6]
}
},
colorMatrixFilterFunctions = {};
for (var t in ColorMatrixMatrixes) ColorMatrixMatrixes.hasOwnProperty(t) && (colorMatrixFilterFunctions[t] = matrixFunction(ColorMatrixMatrixes[t]));
var imageCache = {},
urlCache = {},
loadingIndicator = document.getElementById("loadingIndicator");
NProgress.configure({
parent: "#progressBar"
}),
function() {
var b, a = document.querySelectorAll('input[name = "colorblindType"]');
for (b = 0; b < a.length; b++) a[b].onclick = filterOrImageChanged;
for (a = document.querySelectorAll('input[name = "lens"]'), b = 0; b < a.length; b++) a[b].onclick = lensChanged
}();
var fileInput = document.getElementById("fileInput"),
currentImage;
fileInput.onchange = function(a) {
var b = a.target || window.event.srcElement,
c = b.files;
readFile(c)
};
var canvasDiv = document.getElementById("canvasDiv");
canvasDiv.addEventListener("drop", function(a) {
a.stopPropagation(), a.preventDefault(), readFile(a.dataTransfer.files)
}, !1), canvasDiv.addEventListener("dragover", function(a) {
a.stopPropagation(), a.preventDefault(), a.dataTransfer.dropEffect = "copy"
}, !1), canvasDiv.addEventListener("dragleave", function(a) {}, !1), document.onpaste = function(a) {
for (var b = (a.clipboardData || a.originalEvent.clipboardData).items, c = null, d = 0; d < b.length; d++) 0 === b[d].type.indexOf("image") && (c = b[d].getAsFile());
null !== c && readFile([c])
};
var rBlind = {
protan: {
cpu: .735,
cpv: .265,
am: 1.273463,
ayi: -.073894
},
deutan: {
cpu: 1.14,
cpv: -.14,
am: .968437,
ayi: .003331
},
tritan: {
cpu: .171,
cpv: -.003,
am: .062921,
ayi: .292119
}
},
fBlind = {
Normal: function(a) {
return a
},
Protanopia: function(a) {
return blindMK(a, "protan")
},
Protanomaly: function(a) {
return anomylize(a, blindMK(a, "protan"))
},
Deuteranopia: function(a) {
return blindMK(a, "deutan")
},
Deuteranomaly: function(a) {
return anomylize(a, blindMK(a, "deutan"))
},
Tritanopia: function(a) {
return blindMK(a, "tritan")
},
Tritanomaly: function(a) {
return anomylize(a, blindMK(a, "tritan"))
},
Achromatopsia: function(a) {
return monochrome(a)
},
Achromatomaly: function(a) {
return anomylize(a, monochrome(a))
}
};
For achromatopsia, Coblis seems not to linearize RGB values before the transformation. That makes the whole simulation questionable for me.
Coblis V2 takes his Javascript code from https://github.com/MaPePeR/jsColorblindSimulator . It now uses the "HCIRN Color Blind Simulation function", which is ok but not as accurate as other methods like:
- "Computerized simulation of color appearance for dichromats" by Brettel, Viénot and Mollon (1997)
- "Digital video colourmaps for checking the legibility of displays by dichromats" by Viénot, Brettel and Mollon (1999)
- Or more recently "A Physiologically-based Model for Simulation of Color Vision Deficiency" by Machado, Oliveira & Fernandes (2009)
If you are interested libDaltonLens is a minimalistic public domain implementation of the first two in C, otherwise the Machado precomputed matrices can be found on their website.
In any case, please don't use the "ColorMatrix" from colorjack, the author himself said that it was a very inaccurate one-night hack and that he should probably take his website down, which apparently he did!
Coblis V2 takes his Javascript code from https://github.com/MaPePeR/jsColorblindSimulator . It now uses the "HCIRN Color Blind Simulation function", which is ok but not as accurate as other methods like:
- "Computerized simulation of color appearance for dichromats" by Brettel, Viénot and Mollon (1997)
- "Digital video colourmaps for checking the legibility of displays by dichromats" by Viénot, Brettel and Mollon (1999)
- Or more recently "A Physiologically-based Model for Simulation of Color Vision Deficiency" by Machado, Oliveira & Fernandes (2009)
If you are interested libDaltonLens is a minimalistic public domain implementation of the first two in C, otherwise the Machado precomputed matrices can be found on their website.
In any case, please don't use the "ColorMatrix" from colorjack, the author himself said that it was a very inaccurate one-night hack and that he should probably take his website down, which apparently he did!
Thank you for your detailed comment. I'll leave this code up as a curiosity, but I've updated the header comment directing interested parties to review the comments.
Thank you for providing these matrices. It makes me possible to create a html-js for selecting a color blind friendly color set. A power point file should be color blind friendly, yes absolutely. But, we have other problems.
- If we know that there are only normal and D-type, shall we consider about T-type?
- We have pre-selected color sets those are provided by several professors. Even though a student wants to change, he/she can not. They don't have enough knowledge.
I write a html-js for my studens.
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<style>
.content {
max-width: 1500px;
padding: 10px;
}
tr {height: 50px;}
td {width: 10%; border: 1px solid black;}
</style>
</head>
<body>
<div class="content">
<table id="aTable">
<thead id="aThead"></thead>
<tbody id="aTbody"></tbody>
</table>
I removed all the Japanese sentences. It just explains how to use and something so on. You can see the Japanese version <a href="https://docs.0machi.com/CBF.html">here</a>. I included the link to this page in the HTML.
<script>
const CBOrder = ['Normal','Deuteranopia','Protanopia','Normal','Deuteranomaly','Protanomaly','Tritanopia','Tritanomaly','Achromatopsia','Achromatomaly'];
const InitialColor = ['#FF0000','#FFFF00','#0000FF','#FF0080','#00FF00'];
function CBColor(col, CB)
{
let colorMats =
{
// Normal
'Normal':
[1,0,0,
0,1,0,
0,0,1],
// Green-Blind
'Deuteranopia':
[0.625,0.375,0.000,
0.700,0.300,0.000,
0.000,0.300,0.700],
// Red-Blind
'Protanopia':
[0.567,0.433,0.000,
0.558,0.442,0.000,
0.000,0.242,0.758],
// Green-Weak
'Deuteranomaly':
[0.800,0.200,0.000,
0.258,0.742,0.000,
0.000,0.142,0.858],
// Red-Weak
'Protanomaly':
[0.817,0.183,0.000,
0.333,0.667,0.000,
0.000,0.125,0.875],
// Blue-Blind
'Tritanopia':
[0.950,0.050,0.000,
0.000,0.433,0.567,
0.000,0.475,0.525],
// Blue-Weak
'Tritanomaly':
[0.967,0.033,0.00,
0.00,0.733,0.267,
0.00,0.183,0.817],
// Monochromacy
'Achromatopsia':
[0.299,0.587,0.114,
0.299,0.587,0.114,
0.299,0.587,0.114],
// Blue Cone Monochromacy
'Achromatomaly':
[0.618,0.320,0.062,
0.163,0.775,0.062,
0.163,0.320,0.516]
};
let r, g, b, r2, g2, b2;
let mat = colorMats[CB];
r = parseInt(col.substring(1,3), 16);
g = parseInt(col.substring(3,5), 16);
b = parseInt(col.substring(5,7), 16);
r2 = Math.round(mat[0]*r+mat[1]*g+mat[2]*b);
g2 = Math.round(mat[3]*r+mat[4]*g+mat[5]*b);
b2 = Math.round(mat[6]*r+mat[7]*g+mat[8]*b);
return(`#`+r2.toString(16).padStart(2, '0')+g2.toString(16).padStart(2, '0')+b.toString(16).padStart(2, '0'));
}
function paint1Line(line, col)
{
let cells = line.cells;
for (let i=1;i<cells.length;i++)
{
cells[i].style.backgroundColor=CBColor(col, CBOrder[i-1]);
}
}
let thead = document.getElementById('aThead');
let tr = document.createElement('tr');
let td = document.createElement('td');
tr.appendChild(td);
for (const element of CBOrder) {
let td = document.createElement('td');
td.innerText = element;
tr.appendChild(td);
}
thead.appendChild(tr);
let tbody = document.getElementById('aTbody');
for (const aColor of InitialColor) {
let tr = document.createElement('tr');
let td = document.createElement('td');
let input = document.createElement('input');
input.setAttribute("type", "color");
td.appendChild(input);
tr.appendChild(td);
input.addEventListener("input", onColorBoxClick);
for (let j = 0; j < CBOrder.length; j++) {
let td = document.createElement('td');
tr.appendChild(td);
}
tbody.appendChild(tr);
input.value = aColor;
paint1Line(tr, aColor);
}
function onColorBoxClick(){
paint1Line(event.target.parentElement.parentElement, event.target.value)
}
</script>
</div>
</body>
</html>
Coblis performs it's transform server-side, so I can't access it's source. However, Coblis links to a dead page that used to contain these matrices (I recovered the page via the Wayback Machine). I wrote a go-lang script that performs the transforms and compared the results visually to Coblis output on their test image, and they seem to match.