Method Original Protanopia Deuteranopia Tritanopia Achromatopsia
GLSL precision highp float; uniform sampler2D u_image; varying vec2 vTextureCoordinate; mat4 protanopia = mat4( 0.1120, 0.8853, -0.0005, 0, 0.1126, 0.8897, -0.0001, 0, 0.0045, 0.0001, 1.00191, 0, 0, 0, 0, 1); vec4 srgbToRgb(vec4 color){ bvec4 bcutoff = lessThan(color, vec4(0.04045)); vec4 cutoff = vec4(float(bcutoff[0]), float(bcutoff[1]), float(bcutoff[2]), float(bcutoff[3])); vec4 higher = pow((color + vec4(0.055)) / vec4(1.055), vec4(2.4)); vec4 lower = color / vec4(12.92); return mix(higher, lower, cutoff); } vec4 rgbToSrgb(vec4 color){ bvec4 bcutoff = lessThan(color, vec4(0.0031308)); vec4 cutoff = vec4(float(bcutoff[0]), float(bcutoff[1]), float(bcutoff[2]), float(bcutoff[3])); vec4 higher = vec4(1.055) * pow(color, vec4(1.0 / 2.4)) - 0.055; vec4 lower = color * vec4(12.92); return mix(higher, lower, cutoff); } void main() { vec4 source = texture2D(u_image, vTextureCoordinate); vec4 linearColor = srgbToRgb(source); vec4 linearTarget = linearColor * protanopia; vec4 srgbTarget = rgbToSrgb(linearTarget); gl_FragColor = srgbTarget; } precision mediump float; uniform sampler2D u_image; varying vec2 vTextureCoordinate; mat4 deuteranopia = mat4( 0.2920, 0.7054, -0.0003, 0, 0.2934, 0.7089, 0.0000, 0, -0.02098, 0.02559, 1.0019, 0, 0, 0, 0, 1); vec4 srgbToRgb(vec4 color){ bvec4 bcutoff = lessThan(color, vec4(0.04045)); vec4 cutoff = vec4(float(bcutoff[0]), float(bcutoff[1]), float(bcutoff[2]), float(bcutoff[3])); vec4 higher = pow((color + vec4(0.055)) / vec4(1.055), vec4(2.4)); vec4 lower = color / vec4(12.92); return mix(higher, lower, cutoff); } vec4 rgbToSrgb(vec4 color){ bvec4 bcutoff = lessThan(color, vec4(0.0031308)); vec4 cutoff = vec4(float(bcutoff[0]), float(bcutoff[1]), float(bcutoff[2]), float(bcutoff[3])); vec4 higher = vec4(1.055) * pow(color, vec4(1.0 / 2.4)) - 0.055; vec4 lower = color * vec4(12.92); return mix(higher, lower, cutoff); } void main() { vec4 source = texture2D(u_image, vTextureCoordinate); vec4 linearColor = srgbToRgb(source); vec4 linearTarget = linearColor * deuteranopia; vec4 srgbTarget = rgbToSrgb(linearTarget); gl_FragColor = srgbTarget; } precision highp float; uniform sampler2D u_image; varying vec2 vTextureCoordinate; mat4 achromatopsia = mat4( 0.21, 0.72, 0.07, 0, 0.21, 0.72, 0.07, 0, 0.21, 0.72, 0.07, 0, 0, 0, 0, 1); vec4 srgbToRgb(vec4 color){ bvec4 bcutoff = lessThan(color, vec4(0.04045)); vec4 cutoff = vec4(float(bcutoff[0]), float(bcutoff[1]), float(bcutoff[2]), float(bcutoff[3])); vec4 higher = pow((color + vec4(0.055)) / vec4(1.055), vec4(2.4)); vec4 lower = color / vec4(12.92); return mix(higher, lower, cutoff); } vec4 rgbToSrgb(vec4 color){ bvec4 bcutoff = lessThan(color, vec4(0.0031308)); vec4 cutoff = vec4(float(bcutoff[0]), float(bcutoff[1]), float(bcutoff[2]), float(bcutoff[3])); vec4 higher = vec4(1.055) * pow(color, vec4(1.0 / 2.4)) - 0.055; vec4 lower = color * vec4(12.92); return mix(higher, lower, cutoff); } void main() { vec4 source = texture2D(u_image, vTextureCoordinate); vec4 linearColor = srgbToRgb(source); vec4 linearTarget = linearColor * achromatopsia; vec4 srgbTarget = rgbToSrgb(linearTarget); gl_FragColor = srgbTarget; }
SVG 😞
JS const protanopia = [ [0.1120, 0.8853, -0.0005, 0], [0.1126, 0.8897, -0.0001, 0], [0.0045, 0.0001, 1.00191, 0], [0, 0, 0, 1] ]; function srgbToRgb(color) { return color.map(component => { const sign = component < 0 ? -1 : 1; const abs=Math.abs(component); return abs <= 0.04045 ? component / 12.92 : sign * Math.pow((abs + 0.055) / 1.055, 2.4); }); } function rgbToSrgb(color) { return color.map(component => { return component < 0.0031308 ? component * 12.92 : 1.055 * Math.pow(component, 1/2.4) - 0.055 }); } const linearColor = srgbToRgb(color); const linearResult = Matrix.crossMultiplyMatrixVector(linearColor, protanopia); const srgbResult = rgbToSrgb(linearResult); return srgbResult; const deuteranopia = [ [0.2920, 0.7054, -0.0003, 0], [0.2934, 0.7089, 0.0000, 0], [-0.02098, 0.02559, 1.0019, 0], [0, 0, 0, 1] ]; function srgbToRgb(color) { return color.map(component => { const sign = component < 0 ? -1 : 1; const abs=Math.abs(component); return abs <= 0.04045 ? component / 12.92 : sign * Math.pow((abs + 0.055) / 1.055, 2.4); }); } function rgbToSrgb(color) { return color.map(component => { return component < 0.0031308 ? component * 12.92 : 1.055 * Math.pow(component, 1/2.4) - 0.055 }); } const linearColor = srgbToRgb(color); const linearResult = Matrix.crossMultiplyMatrixVector(linearColor, deuteranopia); const srgbResult = rgbToSrgb(linearResult); return srgbResult; function srgbToRgb(color) { return color.map(component => { const sign = component < 0 ? -1 : 1; const abs=Math.abs(component); return abs <= 0.04045 ? component / 12.92 : sign * Math.pow((abs + 0.055) / 1.055, 2.4); }); } function rgbToSrgb(color) { return color.map(component => { return component < 0.0031308 ? component * 12.92 : 1.055 * Math.pow(component, 1/2.4) - 0.055 }); } const e = [65.5178, 34.4782, 1.68427, 1]; //RGB white in LMS space function lmsColorToTritanopia([l, m, s, alpha]) { //input "Q" const anchor = (m / l) < (e[1] / e[0]) // anchor "A" ? [0.0930085, 0.00730255, 0.0] //660nm : [0.163952, 0.268063, 0.290322] //485nm const a = (e[1] * anchor[2]) - (e[2] * anchor[1]); const b = (e[2] * anchor[0]) - (e[0] * anchor[2]); const c = (e[0] * anchor[1]) - (e[1] * anchor[0]); return [l, m, -((a * l) + (b * m)) / c, alpha]; } //Hunt-Pointer-Estevez const rgbToLms = [ [31.3989492, 63.95129383, 4.64975462, 0], [15.53714069, 75.78944616, 8.67014186, 0], [1.77515606, 10.94420944, 87.25692246, 0], [0, 0, 0, 1] ]; const lmsToRgb = [ [0.0547, -0.0464, 0.0017, 0], [-0.0112, 0.0229, -0.0017, 0], [0.0002, -0.0019, 0.0116, 0], [0, 0, 0, 1] ]; const linearColor = srgbToRgb(color); const lmsColor = Matrix.crossMultiplyMatrixVector(linearColor, rgbToLms); const lmsTritanopia = lmsColorToTritanopia(lmsColor); const rgbTritanopia = Matrix.crossMultiplyMatrixVector(lmsTritanopia, lmsToRgb); const srgbResult = rgbToSrgb(rgbTritanopia); return srgbResult; const achromatopsia = [ [0.21, 0.72, 0.07, 0], [0.21, 0.72, 0.07, 0], [0.21, 0.72, 0.07, 0], [0, 0, 0, 1], ]; function srgbToRgb(color) { return color.map(component => { const sign = component < 0 ? -1 : 1; const abs=Math.abs(component); return abs <= 0.04045 ? component / 12.92 : sign * Math.pow((abs + 0.055) / 1.055, 2.4); }); } function rgbToSrgb(color) { return color.map(component=> { return component < 0.0031308 ? component * 12.92 : 1.055 * Math.pow(component, 1/2.4) - 0.055 }); } const linearColor = srgbToRgb(color); const linearResult = Matrix.crossMultiplyMatrixVector(color, achromatopsia); const srgbResult = rgbToSrgb(linearResult); return srgbResult;
WGSL
Chrome Dev Tools (SVG)
GIMP Filters (PNG)
Original Protanopia Deuteranopia Tritanopia (fixed) Achromatopsia