|
| 1 | +// Color Vision Shader for Hyprland - Simulates and compensates for color vision deficiency (CVD) |
| 2 | +// by: khing |
| 3 | + |
| 4 | +/********************************************************************************************** |
| 5 | + * DEV NOTES: * |
| 6 | + * PLEASE NOTE THAT I DON'T HAVE ANY KNOWLEDGE OF GLSL, SO THIS SHADER MAY NOT BE OPTIMAL. * |
| 7 | + * I ONLY SEARCHED ONLINE FOR WAYS TO SIMULATE COLOR VISION DEFICIENCY AND DALTONIZATION. * |
| 8 | + * I ONLY TESTED THIS USING AN ANDROID APP CALLED "CVSIMULATOR". * |
| 9 | + * IF YOU HAVE ANY SUGGESTIONS FOR IMPROVEMENTS, PLEASE LET ME KNOW! * |
| 10 | + **********************************************************************************************/ |
| 11 | + |
| 12 | + |
| 13 | +/* |
| 14 | +To override this parameters create a file named './color-vision.inc' |
| 15 | +We only need to match the file name and use 'inc' to incdicate that |
| 16 | + this is an "include" file |
| 17 | + Example: |
| 18 | + ┌────────────────────────────────────────────────────────────────────────────┐ |
| 19 | + │ //file: ./color-vision.inc │ |
| 20 | + │ // integer: 0:Normal vision, 1:Protanopia, 2:Deuteranopia, 3:Tritanopia │ |
| 21 | + │ #define COLOR_VISION_MODE 1 │ |
| 22 | + │ // float: 0.0:No effect, -1.0 daltonization, 1.0: CVD simulation │ |
| 23 | + │ #define COLOR_VISION_INTENSITY 0.5 │ |
| 24 | + │ │ |
| 25 | + └────────────────────────────────────────────────────────────────────────────┘ |
| 26 | + */ |
| 27 | + |
| 28 | + |
| 29 | +#ifndef COLOR_VISION_MODE |
| 30 | + #define COLOR_VISION_MODE 0 // Default fallback value |
| 31 | +#endif |
| 32 | +#ifndef COLOR_VISION_INTENSITY |
| 33 | + #define COLOR_VISION_INTENSITY 0.0 // Default fallback value |
| 34 | +#endif |
| 35 | + |
| 36 | +/* |
| 37 | + ┌─────────────────────────────────────────────────────────────────────────┐ |
| 38 | + !│ DO NOT EDIT THE FOLLOWING LINES │ |
| 39 | + └─────────────────────────────────────────────────────────────────────────┘ |
| 40 | + */ |
| 41 | + |
| 42 | +precision highp float; |
| 43 | +varying vec2 v_texcoord; |
| 44 | +uniform sampler2D tex; |
| 45 | + |
| 46 | + |
| 47 | +const int MODE = COLOR_VISION_MODE; |
| 48 | +const float INTENSITY = COLOR_VISION_INTENSITY; |
| 49 | + |
| 50 | + |
| 51 | + |
| 52 | +// Constants for color vision deficiency simulation |
| 53 | +const float protanopia_r = 2.02344; |
| 54 | +const float protanopia_g = -2.52581; |
| 55 | + |
| 56 | +const float deuteranopia_r = 0.494207; |
| 57 | +const float deuteranopia_g = 1.24827; |
| 58 | + |
| 59 | +const float tritanopia_r = -0.395913; |
| 60 | +const float tritanopia_g = 0.801109; |
| 61 | + |
| 62 | + |
| 63 | + |
| 64 | +// RGB to LMS conversion matrix |
| 65 | +const mat3 RGB2LMS = mat3( |
| 66 | + 17.8824, 43.5161, 4.11935, |
| 67 | + 3.45565, 27.1554, 3.86714, |
| 68 | + 0.0299566, 0.184309, 1.46709 |
| 69 | +); |
| 70 | + |
| 71 | +// LMS to RGB conversion matrix |
| 72 | +const mat3 LMS2RGB = mat3( |
| 73 | + 0.0809444479, -0.130504409, 0.116721066, |
| 74 | + -0.0102485335, 0.0540193266, -0.113614708, |
| 75 | + -0.000365296938, -0.00412161469, 0.693511405 |
| 76 | +); |
| 77 | + |
| 78 | +// Simulate color vision deficiency based on LMS color space transformations |
| 79 | +vec3 simulateColorVisionDeficiency(vec3 color) { |
| 80 | + // Convert from RGB to LMS color space (Long, Medium, Short cone response) |
| 81 | + vec3 lms = RGB2LMS * color; |
| 82 | + |
| 83 | + // Apply CVD transformation based on selected mode |
| 84 | + mat3 m; |
| 85 | + if(MODE == 0) { // Normal vision (no transformation) |
| 86 | + m = mat3( |
| 87 | + 1.0, 0.0, 0.0, |
| 88 | + 0.0, 1.0, 0.0, |
| 89 | + 0.0, 0.0, 1.0 |
| 90 | + ); |
| 91 | + } else if(MODE == 1) { // Protanopia (red-deficient) |
| 92 | + m = mat3( |
| 93 | + 0.0, protanopia_r, protanopia_g, |
| 94 | + 0.0, 1.0, 0.0, |
| 95 | + 0.0, 0.0, 1.0 |
| 96 | + ); |
| 97 | + } else if(MODE == 2) { // Deuteranopia (green-deficient) |
| 98 | + m = mat3( |
| 99 | + 1.0, 0.0, 0.0, |
| 100 | + deuteranopia_r, 0.0, deuteranopia_g, |
| 101 | + 0.0, 0.0, 1.0 |
| 102 | + ); |
| 103 | + } else { // Tritanopia (blue-deficient) |
| 104 | + m = mat3( |
| 105 | + 1.0, 0.0, 0.0, |
| 106 | + 0.0, 1.0, 0.0, |
| 107 | + tritanopia_r, tritanopia_g, 0.0 |
| 108 | + ); |
| 109 | + } |
| 110 | + |
| 111 | + // Transform back to RGB color space |
| 112 | + return LMS2RGB * (m * lms); |
| 113 | +} |
| 114 | + |
| 115 | +// Apply daltonization to enhance color differences for CVD viewers |
| 116 | +vec3 daltonize(vec3 color, vec3 simulation) { |
| 117 | + // Calculate the error between original and simulated colors |
| 118 | + vec3 error = color - simulation; |
| 119 | + |
| 120 | + // Redistribute the error to enhance visibility |
| 121 | + vec3 correction; |
| 122 | + if (MODE == 0) { // Normal vision - no correction needed |
| 123 | + correction = vec3(0.0, 0.0, 0.0); |
| 124 | + } else if (MODE == 1) { // Protanopia - shift errors in red to blue and green |
| 125 | + correction = vec3(0.0, error.r * 0.7, error.r * 0.3); |
| 126 | + } else if (MODE == 2) { // Deuteranopia - shift errors in green to red and blue |
| 127 | + correction = vec3(error.g * 0.7, 0.0, error.g * 0.3); |
| 128 | + } else { // Tritanopia - shift errors in blue to red and green |
| 129 | + correction = vec3(error.b * 0.5, error.b * 0.5, 0.0); |
| 130 | + } |
| 131 | + |
| 132 | + // Apply correction |
| 133 | + return color + correction; |
| 134 | +} |
| 135 | + |
| 136 | +void main() { |
| 137 | + // Sample the texture at the current fragment's texture coordinates |
| 138 | + vec4 pixColor = texture2D(tex, v_texcoord); |
| 139 | + vec3 color = pixColor.rgb; |
| 140 | + |
| 141 | + // No effect needed for normal vision with no intensity |
| 142 | + if (MODE == 0 && INTENSITY == 0.0) { |
| 143 | + gl_FragColor = pixColor; |
| 144 | + return; |
| 145 | + } |
| 146 | + |
| 147 | + // Simulate color vision deficiency based on the selected mode |
| 148 | + vec3 simulated = simulateColorVisionDeficiency(color); |
| 149 | + |
| 150 | + // Optional: Apply daltonization (color correction) if intensity is negative |
| 151 | + // This helps make colors more distinguishable for users with CVD |
| 152 | + vec3 corrected = daltonize(color, simulated); |
| 153 | + |
| 154 | + vec3 result; |
| 155 | + |
| 156 | + // Special handling for normal vision mode |
| 157 | + if (MODE == 0) { |
| 158 | + // For normal vision, positive intensity increases saturation |
| 159 | + // and negative intensity decreases saturation (towards grayscale) |
| 160 | + if (INTENSITY >= 0.0) { |
| 161 | + // Increase saturation |
| 162 | + vec3 luminance = vec3(dot(color, vec3(0.2126, 0.7152, 0.0722))); |
| 163 | + result = mix(color, mix(luminance, color * 1.5, 1.0), INTENSITY); |
| 164 | + } else { |
| 165 | + // Decrease saturation (towards grayscale) |
| 166 | + vec3 luminance = vec3(dot(color, vec3(0.2126, 0.7152, 0.0722))); |
| 167 | + result = mix(color, luminance, -INTENSITY); |
| 168 | + } |
| 169 | + } |
| 170 | + // For color vision deficiency modes |
| 171 | + else if (INTENSITY >= 0.0) { |
| 172 | + // Apply simulation with gradually increasing intensity |
| 173 | + result = mix(color, simulated, INTENSITY); |
| 174 | + } else { |
| 175 | + // Apply daltonization correction with gradually increasing intensity |
| 176 | + result = mix(color, corrected, -INTENSITY); |
| 177 | + } |
| 178 | + |
| 179 | + // Output the final color with the original alpha value |
| 180 | + gl_FragColor = vec4(result, pixColor.a); |
| 181 | +} |
0 commit comments