package com.intellij.ide.ui; import java.awt.image.ImageFilter; final class SimulationFilter extends WeightFilter { public static ImageFilter get(ColorBlindness blindness) { if (blindness == ColorBlindness.protanopia) return protanopia; if (blindness == ColorBlindness.deuteranopia) return deuteranopia; if (blindness == ColorBlindness.tritanopia) return tritanopia; if (blindness == ColorBlindness.achromatopsia) return achromatopsia; return null; } public static final ImageFilter protanopia = forProtanopia(null); public static final ImageFilter deuteranopia = forDeuteranopia(null); public static final ImageFilter tritanopia = forTritanopia(null); public static final ImageFilter achromatopsia = forAchromatopsia(null); public static ImageFilter forProtanopia(Double weight) { return new SimulationFilter("Protanopia", weight, 0.7465, 0.2535, 1.273463, -0.073894); } public static ImageFilter forDeuteranopia(Double weight) { return new SimulationFilter("Deuteranopia", weight, 1.4, -0.4, 0.968437, 0.003331); } public static ImageFilter forTritanopia(Double weight) { return new SimulationFilter("Tritanopia", weight, 0.1748, 0, 0.062921, 0.292119); } public static ImageFilter forAchromatopsia(Double weight) { return new WeightFilter("Achromatopsia (simulation)", weight) { @Override int toRGB(int srcR, int srcG, int srcB) { double gray = 0.212656 * srcR + 0.715158 * srcG + 0.072186 * srcB; return toRGB(srcR, srcG, srcB, gray, gray, gray); } }; } private final double myConfuseX; private final double myConfuseY; private final double myConfuseM; private final double myConfuseYint; private SimulationFilter(String name, Double weight, double x, double y, double m, double yint) { super(name + " (simulation)", weight); myConfuseX = x; myConfuseY = y; myConfuseM = m; myConfuseYint = yint; } @Override int toRGB(int srcR, int srcG, int srcB) { // Convert source color into XYZ color space double powR = Math.pow(srcR, 2.2); double powG = Math.pow(srcG, 2.2); double powB = Math.pow(srcB, 2.2); // RGB->XYZ (sRGB:D65) double X = 0.4124240 * powR + 0.357579 * powG + 0.1804640 * powB; double Y = 0.2126560 * powR + 0.715158 * powG + 0.0721856 * powB; double Z = 0.0193324 * powR + 0.119193 * powG + 0.9504440 * powB; // Convert XYZ into xyY Chromacity Coordinates (xy) and Luminance (Y) double chroma_x = X / (X + Y + Z); double chroma_y = Y / (X + Y + Z); // Generate the "Confusion Line" between the source color and the Confusion Point double m = (chroma_y - myConfuseY) / (chroma_x - myConfuseX); // slope of Confusion Line double yint = chroma_y - chroma_x * m; // y-intercept of confusion line (x-intercept = 0.0) // How far the xy coords deviate from the simulation double deviate_x = (myConfuseYint - yint) / (m - myConfuseM); double deviate_y = (m * deviate_x) + yint; // Compute the simulated color's XYZ coords X = deviate_x * Y / deviate_y; Z = (1.0 - (deviate_x + deviate_y)) * Y / deviate_y; // Neutral grey calculated from luminance (in D65) double neutral_X = Y * 0.312713 / 0.329016; double neutral_Z = Y * 0.358271 / 0.329016; // Difference between simulated color and neutral grey double diffX = neutral_X - X; double diffZ = neutral_Z - Z; // XYZ->RGB (sRGB:D65) double diffR = +3.2407100 * diffX - 0.4985710 * diffZ; double diffG = -0.9692580 * diffX + 0.0415557 * diffZ; double diffB = +0.0556352 * diffX + 1.0570700 * diffZ; // Convert to RGB color space // XYZ->RGB (sRGB:D65) double dstR = +3.2407100 * X - 1.537260 * Y - 0.4985710 * Z; double dstG = -0.9692580 * X + 1.875990 * Y + 0.0415557 * Z; double dstB = +0.0556352 * X - 0.203996 * Y + 1.0570700 * Z; // Compensate simulated color towards a neutral fit in RGB space double fitR = ((dstR < 0 ? 0 : 1) - dstR) / diffR; double fitG = ((dstG < 0 ? 0 : 1) - dstG) / diffG; double fitB = ((dstB < 0 ? 0 : 1) - dstB) / diffB; double adjust = Math.max(Math.max( // highest value (fitR < 0 || 1 < fitR) ? 0 : fitR, (fitG < 0 || 1 < fitG) ? 0 : fitG), (fitB < 0 || 1 < fitB) ? 0 : fitB); // Shift proportional to the greatest shift dstR += adjust * diffR; dstG += adjust * diffG; dstB += adjust * diffB; // Apply gamma correction dstR = Math.pow(dstR, 1 / 2.2); dstG = Math.pow(dstG, 1 / 2.2); dstB = Math.pow(dstB, 1 / 2.2); return toRGB(srcR, srcG, srcB, dstR, dstG, dstB); } }