/* * Copyright (c) 2005-2016 Substance Kirill Grouchnikov. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o 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. * * o Neither the name of Substance Kirill Grouchnikov nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 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 tools.uidebug; import java.awt.Color; import org.pushingpixels.substance.api.SubstanceColorScheme; import org.pushingpixels.substance.api.colorscheme.BaseColorScheme; /** * Base class for color schemes simulating color-blind users. * * @author Kirill Grouchnikov */ public class ColorBlindColorScheme extends BaseColorScheme { /** * Matrix for converting RGB to LMS. */ public double[][] _rgbToLms = { { 0.05059983, 0.08585369, 0.00952420 }, { 0.01893033, 0.08925308, 0.01370054 }, { 0.00292202, 0.00975732, 0.07145979 } }; /** * Matrix for converting LMS to RGB. */ public double[][] _lmsToRgb = { { 30.830854, -29.832659, 1.610474 }, { -6.481468, 17.715578, -2.532642 }, { -0.375690, -1.199062, 14.273846 } }; /** * The main ultra-light color. */ private Color mainUltraLightColor; /** * The main extra-light color. */ private Color mainExtraLightColor; /** * The main light color. */ private Color mainLightColor; /** * The main medium color. */ private Color mainMidColor; /** * The main dark color. */ private Color mainDarkColor; /** * The main ultra-dark color. */ private Color mainUltraDarkColor; /** * The foreground color. */ private Color foregroundColor; /** * The original color scheme. */ private SubstanceColorScheme origScheme; /** * Blindness kind. * * @author Kirill Grouchnikov */ public enum BlindnessKind { /** * Protanopia color blindness. */ PROTANOPIA, /** * Deuteranopia color blindness. */ DEUTERANOPIA, /** * Tritanopia color blindness. */ TRITANOPIA } /** * Creates a new color scheme that simulates color-blindness. * * @param origScheme * Original color scheme. * @param kind * Color-blindness kind. */ public ColorBlindColorScheme(SubstanceColorScheme origScheme, BlindnessKind kind) { super(kind.name() + " " + origScheme.getDisplayName(), origScheme .isDark()); this.origScheme = origScheme; this.foregroundColor = getColorBlindColor(origScheme .getForegroundColor(), _rgbToLms, kind, _lmsToRgb); this.mainUltraDarkColor = getColorBlindColor(origScheme .getUltraDarkColor(), _rgbToLms, kind, _lmsToRgb); this.mainDarkColor = getColorBlindColor(origScheme.getDarkColor(), _rgbToLms, kind, _lmsToRgb); this.mainMidColor = getColorBlindColor(origScheme.getMidColor(), _rgbToLms, kind, _lmsToRgb); this.mainLightColor = getColorBlindColor(origScheme.getLightColor(), _rgbToLms, kind, _lmsToRgb); this.mainExtraLightColor = getColorBlindColor(origScheme .getExtraLightColor(), _rgbToLms, kind, _lmsToRgb); this.mainUltraLightColor = getColorBlindColor(origScheme .getUltraLightColor(), _rgbToLms, kind, _lmsToRgb); } /* * (non-Javadoc) * * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor() */ public Color getForegroundColor() { return this.foregroundColor; } /* * (non-Javadoc) * * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor() */ public Color getUltraLightColor() { return this.mainUltraLightColor; } /* * (non-Javadoc) * * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor() */ public Color getExtraLightColor() { return this.mainExtraLightColor; } /* * (non-Javadoc) * * @see org.pushingpixels.substance.color.ColorScheme#getLightColor() */ public Color getLightColor() { return this.mainLightColor; } /* * (non-Javadoc) * * @see org.pushingpixels.substance.color.ColorScheme#getMidColor() */ public Color getMidColor() { return this.mainMidColor; } /* * (non-Javadoc) * * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor() */ public Color getDarkColor() { return this.mainDarkColor; } /* * (non-Javadoc) * * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor() */ public Color getUltraDarkColor() { return this.mainUltraDarkColor; } /** * Returns the original color scheme. * * @return The original color scheme. */ public SubstanceColorScheme getOrigScheme() { return this.origScheme; } /** * Converts the specified color into color-blind version. * * @param orig * The original color. * @param rgbToLms * RGB to LMS conversion matrix. * @param kind * Color-blindness kind. * @param lmsToRgb * LMS to RGB conversion matrix. * @return Color-blind version of the original color. */ private static Color getColorBlindColor(Color orig, double[][] rgbToLms, BlindnessKind kind, double[][] lmsToRgb) { double r = orig.getRed(); double g = orig.getGreen(); double b = orig.getBlue(); double[] rgbOrig = new double[] { r, g, b }; double[] lms = mult3(rgbToLms, rgbOrig); double tmp = 0.0; double[] anchor = { 0.08008, 0.1579, 0.5897, 0.1284, 0.2237, 0.3636, 0.9856, 0.7325, 0.001079, 0.0914, 0.007009, 0.0 }; double[] rgbAnchor = { rgbToLms[0][0] + rgbToLms[0][1] + rgbToLms[0][2], rgbToLms[1][0] + rgbToLms[1][1] + rgbToLms[1][2], rgbToLms[2][0] + rgbToLms[2][1] + rgbToLms[2][2] }; double a1, a2, b1, b2, c1, c2, inflection; switch (kind) { case PROTANOPIA: a1 = rgbAnchor[1] * anchor[8] - rgbAnchor[2] * anchor[7]; b1 = rgbAnchor[2] * anchor[6] - rgbAnchor[0] * anchor[8]; c1 = rgbAnchor[0] * anchor[7] - rgbAnchor[1] * anchor[6]; a2 = rgbAnchor[1] * anchor[2] - rgbAnchor[2] * anchor[1]; b2 = rgbAnchor[2] * anchor[0] - rgbAnchor[0] * anchor[2]; c2 = rgbAnchor[0] * anchor[1] - rgbAnchor[1] * anchor[0]; inflection = rgbAnchor[2] / rgbAnchor[1]; tmp = lms[2] / lms[1]; if (tmp < inflection) lms[0] = -(b1 * lms[1] + c1 * lms[2]) / a1; else lms[0] = -(b2 * lms[1] + c2 * lms[2]) / a2; break; case DEUTERANOPIA: a1 = rgbAnchor[1] * anchor[8] - rgbAnchor[2] * anchor[7]; b1 = rgbAnchor[2] * anchor[6] - rgbAnchor[0] * anchor[8]; c1 = rgbAnchor[0] * anchor[7] - rgbAnchor[1] * anchor[6]; a2 = rgbAnchor[1] * anchor[2] - rgbAnchor[2] * anchor[1]; b2 = rgbAnchor[2] * anchor[0] - rgbAnchor[0] * anchor[2]; c2 = rgbAnchor[0] * anchor[1] - rgbAnchor[1] * anchor[0]; inflection = rgbAnchor[2] / rgbAnchor[0]; tmp = lms[2] / lms[0]; /* See which side of the inflection line we fall... */ if (tmp < inflection) lms[1] = -(a1 * lms[0] + c1 * lms[2]) / b1; else lms[1] = -(a2 * lms[0] + c2 * lms[2]) / b2; break; case TRITANOPIA: a1 = rgbAnchor[1] * anchor[11] - rgbAnchor[2] * anchor[10]; b1 = rgbAnchor[2] * anchor[9] - rgbAnchor[0] * anchor[11]; c1 = rgbAnchor[0] * anchor[10] - rgbAnchor[1] * anchor[9]; a2 = rgbAnchor[1] * anchor[5] - rgbAnchor[2] * anchor[4]; b2 = rgbAnchor[2] * anchor[3] - rgbAnchor[0] * anchor[5]; c2 = rgbAnchor[0] * anchor[4] - rgbAnchor[1] * anchor[3]; inflection = (rgbAnchor[1] / rgbAnchor[0]); tmp = lms[1] / lms[0]; if (tmp < inflection) lms[2] = -(a1 * lms[0] + b1 * lms[1]) / c1; else lms[2] = -(a2 * lms[0] + b2 * lms[1]) / c2; break; default: break; } double[] rgbCb = mult3(lmsToRgb, lms); double nr = Math.min(255.0, Math.max(0.0, rgbCb[0])); double ng = Math.min(255.0, Math.max(0.0, rgbCb[1])); double nb = Math.min(255.0, Math.max(0.0, rgbCb[2])); return new Color((int) nr, (int) ng, (int) nb); } /** * Multiplies the specified 3x3 matrix by the specified 3x1 vector. * * @param matrix * Matrix. * @param vector * Vector. * @return Vector multiplication. */ private static double[] mult3(double[][] matrix, double[] vector) { double[] res = new double[3]; res[0] = matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2]; res[1] = matrix[1][0] * vector[0] + matrix[1][1] * vector[1] + matrix[1][2] * vector[2]; res[2] = matrix[2][0] * vector[0] + matrix[2][1] * vector[1] + matrix[2][2] * vector[2]; return res; } }