diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index 5f458f1762..67b2c1bcc1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -252,6 +252,8 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private int modelOrientation; // Uniforms + private int uniColorBlindMode; + private int uniUiColorBlindMode; private int uniUseFog; private int uniFogColor; private int uniFogDepth; @@ -536,11 +538,13 @@ public class GpuPlugin extends Plugin implements DrawCallbacks uniFogColor = gl.glGetUniformLocation(glProgram, "fogColor"); uniFogDepth = gl.glGetUniformLocation(glProgram, "fogDepth"); uniDrawDistance = gl.glGetUniformLocation(glProgram, "drawDistance"); + uniColorBlindMode = gl.glGetUniformLocation(glProgram, "colorBlindMode"); uniTex = gl.glGetUniformLocation(glUiProgram, "tex"); uniTexSamplingMode = gl.glGetUniformLocation(glUiProgram, "samplingMode"); uniTexTargetDimensions = gl.glGetUniformLocation(glUiProgram, "targetDimensions"); uniTexSourceDimensions = gl.glGetUniformLocation(glUiProgram, "sourceDimensions"); + uniUiColorBlindMode = gl.glGetUniformLocation(glUiProgram, "colorBlindMode"); uniTextures = gl.glGetUniformLocation(glProgram, "textures"); uniTextureOffsets = gl.glGetUniformLocation(glProgram, "textureOffsets"); @@ -1121,6 +1125,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks // Brightness happens to also be stored in the texture provider, so we use that gl.glUniform1f(uniBrightness, (float) textureProvider.getBrightness()); gl.glUniform1f(uniSmoothBanding, config.smoothBanding() ? 0f : 1f); + gl.glUniform1i(uniColorBlindMode, config.colorBlindMode().ordinal()); // Calculate projection matrix Matrix4 projectionMatrix = new Matrix4(); @@ -1248,6 +1253,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks gl.glUniform1i(uniTex, 0); gl.glUniform1i(uniTexSamplingMode, uiScalingMode.getMode()); gl.glUniform2i(uniTexSourceDimensions, canvasWidth, canvasHeight); + gl.glUniform1i(uniUiColorBlindMode, config.colorBlindMode().ordinal()); if (client.isStretchedEnabled()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPluginConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPluginConfig.java index c432ba4ad7..bef0ce2921 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPluginConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPluginConfig.java @@ -31,6 +31,7 @@ import net.runelite.client.config.Range; import static net.runelite.client.plugins.gpu.GpuPlugin.MAX_FOG_DEPTH; import net.runelite.client.plugins.gpu.config.AntiAliasingMode; import static net.runelite.client.plugins.gpu.GpuPlugin.MAX_DISTANCE; +import net.runelite.client.plugins.gpu.config.ColorBlindMode; import net.runelite.client.plugins.gpu.config.UIScalingMode; @ConfigGroup("gpu") @@ -123,4 +124,15 @@ public interface GpuPluginConfig extends Config { return 0; } + + @ConfigItem( + keyName = "colorBlindMode", + name = "Colorblindness Correction", + description = "Adjusts colors to account for colorblindness", + position = 8 + ) + default ColorBlindMode colorBlindMode() + { + return ColorBlindMode.NONE; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/ColorBlindMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/ColorBlindMode.java new file mode 100644 index 0000000000..9a544c0e6a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/ColorBlindMode.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Ben Poulson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.gpu.config; + +public enum ColorBlindMode +{ + NONE, + PROTANOPE, + DEUTERANOPE, + TRITANOPE; +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/colorblind.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/colorblind.glsl new file mode 100644 index 0000000000..2a6a16883d --- /dev/null +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/colorblind.glsl @@ -0,0 +1,74 @@ +// +// Algorithm from "Analysis of Color Blindness" by Onur Fidaner, Poliang Lin and Nevran Ozguven. +// https://web.archive.org/web/20090731011248/http://scien.stanford.edu/class/psych221/projects/05/ofidaner/project_report.pdf +// + +#define NONE 0 +#define PROTAN 1 +#define DEUTERAN 2 +#define TRITAN 3 + +const mat3 rgb2lms = mat3( + vec3(17.8824, 43.5161, 4.11935), + vec3(3.45565, 27.1554, 3.86714), + vec3(0.0299566, 0.184309, 1.46709) +); + +const mat3 lms2lmsp = mat3( + vec3(0.0, 2.02344, -2.52581), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +const mat3 lms2lmsd = mat3( + vec3(1.0, 0.0, 0.0), + vec3(0.494207, 0.0, 1.24827), + vec3(0.0, 0.0, 1.0) +); + +const mat3 lms2lmst = mat3( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(-0.395913, 0.801109, 0.0) +); + +const mat3 corrections = mat3( + vec3(0.0, 0.0, 0.0), + vec3(0.7, 1.0, 0.0), + vec3(0.7, 0.0, 1.0) +); + +vec3 colorblind(int mode, vec3 color) +{ + vec3 LMS = color * rgb2lms; + vec3 lms; + + if (mode == PROTAN) { + lms = LMS * lms2lmsp; // red deficiency + } + else if (mode == DEUTERAN) { + lms = LMS * lms2lmsd; // green deficiency + } + else if (mode == TRITAN) { + lms = LMS * lms2lmst; // blue deficiency + } + else { + // Should be impossible to get here + return color; + } + + // LMS to RGB matrix conversion + mat3 lms2rgb = inverse(rgb2lms); + vec3 error = lms * lms2rgb; + + // Isolate invisible colors to color vision deficiency (calculate error matrix) + error = (color - error); + + // Shift colors towards visible spectrum (apply error modifications) + vec3 correction = error * corrections; + + // Add compensation to original values + correction = color + correction; + + return correction; +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl index 54ddd3cfe6..b89513a94e 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl @@ -29,6 +29,7 @@ uniform vec2 textureOffsets[64]; uniform float brightness; uniform float smoothBanding; uniform vec4 fogColor; +uniform int colorBlindMode; in vec4 Color; noperspective centroid in float fHsl; @@ -39,6 +40,7 @@ in float fogAmount; out vec4 FragColor; #include hsl_to_rgb.glsl +#include colorblind.glsl void main() { int hsl = int(fHsl); @@ -57,6 +59,10 @@ void main() { smoothColor = textureColorBrightness * smoothColor; } + if (colorBlindMode > 0) { + smoothColor.rgb = colorblind(colorBlindMode, smoothColor.rgb); + } + vec3 mixedColor = mix(smoothColor.rgb, fogColor.rgb, fogAmount); FragColor = vec4(mixedColor, smoothColor.a); } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui.glsl index 73a3e36ee7..cf013aacdd 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui.glsl @@ -34,9 +34,11 @@ uniform sampler2D tex; uniform int samplingMode; uniform ivec2 sourceDimensions; uniform ivec2 targetDimensions; +uniform int colorBlindMode; #include scale/bicubic.glsl #include scale/xbr_lv2_frag.glsl +#include colorblind.glsl in vec2 TexCoord; in XBRTable xbrTable; @@ -53,5 +55,9 @@ void main() { else if (samplingMode == SAMPLING_XBR) c = textureXBR(tex, TexCoord, xbrTable, ceil(1.0 * targetDimensions.x / sourceDimensions.x)); + if (colorBlindMode > 0) { + c.rgb = colorblind(colorBlindMode, c.rgb); + } + FragColor = c; }