1ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian/* 2ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * Copyright 2013 The Android Open Source Project 3ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * 4ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * Licensed under the Apache License, Version 2.0 (the "License"); 5ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * you may not use this file except in compliance with the License. 6ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * You may obtain a copy of the License at 7ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * 8ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * http://www.apache.org/licenses/LICENSE-2.0 9ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * 10ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * Unless required by applicable law or agreed to in writing, software 11ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * distributed under the License is distributed on an "AS IS" BASIS, 12ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * See the License for the specific language governing permissions and 14ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian * limitations under the License. 15ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian */ 16ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 17ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian#include "Daltonizer.h" 18ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian#include <ui/mat4.h> 19ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 20ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopiannamespace android { 21ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 229f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stozavoid Daltonizer::setType(ColorBlindnessType type) { 23ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian if (type != mType) { 24ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian mDirty = true; 25ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian mType = type; 26ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian } 27ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian} 28ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 299f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stozavoid Daltonizer::setMode(ColorBlindnessMode mode) { 30ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian if (mode != mMode) { 31ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian mDirty = true; 32ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian mMode = mode; 33ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian } 34ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian} 35ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 36ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopianconst mat4& Daltonizer::operator()() { 37ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian if (mDirty) { 38ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian mDirty = false; 39ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian update(); 40ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian } 41ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian return mColorTransform; 42ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian} 43ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 44ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopianvoid Daltonizer::update() { 459f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza if (mType == ColorBlindnessType::None) { 469f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza mColorTransform = mat4(); 479f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza return; 489f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza } 499f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza 50ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // converts a linear RGB color to the XYZ space 51ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 rgb2xyz( 0.4124, 0.2126, 0.0193, 0, 52ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.3576, 0.7152, 0.1192, 0, 53ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.1805, 0.0722, 0.9505, 0, 54ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0 , 0 , 0 , 1); 55ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 56ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // converts a XYZ color to the LMS space. 57ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 xyz2lms( 0.7328,-0.7036, 0.0030, 0, 58ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.4296, 1.6975, 0.0136, 0, 59ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian -0.1624, 0.0061, 0.9834, 0, 60ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0 , 0 , 0 , 1); 61ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 62ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // Direct conversion from linear RGB to LMS 63ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 rgb2lms(xyz2lms*rgb2xyz); 64ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 65ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // And back from LMS to linear RGB 66ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 lms2rgb(inverse(rgb2lms)); 67ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 68ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // To simulate color blindness we need to "remove" the data lost by the absence of 69ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // a cone. This cannot be done by just zeroing out the corresponding LMS component 70ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // because it would create a color outside of the RGB gammut. 71ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // Instead we project the color along the axis of the missing component onto a plane 72ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // within the RGB gammut: 73ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // - since the projection happens along the axis of the missing component, a 74ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // color blind viewer perceives the projected color the same. 75ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // - We use the plane defined by 3 points in LMS space: black, white and 76ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // blue and red for protanopia/deuteranopia and tritanopia respectively. 77ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 78ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // LMS space red 79ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const vec3& lms_r(rgb2lms[0].rgb); 80ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // LMS space blue 81ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const vec3& lms_b(rgb2lms[2].rgb); 82ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // LMS space white 83ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const vec3 lms_w((rgb2lms * vec4(1)).rgb); 84ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 85ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // To find the planes we solve the a*L + b*M + c*S = 0 equation for the LMS values 86ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // of the three known points. This equation is trivially solved, and has for 87ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // solution the following cross-products: 88ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const vec3 p0 = cross(lms_w, lms_b); // protanopia/deuteranopia 89ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const vec3 p1 = cross(lms_w, lms_r); // tritanopia 90ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 91ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // The following 3 matrices perform the projection of a LMS color onto the given plane 92ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // along the selected axis 93ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 94ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // projection for protanopia (L = 0) 95ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 lms2lmsp( 0.0000, 0.0000, 0.0000, 0, 96ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian -p0.y / p0.x, 1.0000, 0.0000, 0, 97ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian -p0.z / p0.x, 0.0000, 1.0000, 0, 98ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0 , 0 , 0 , 1); 99ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 100ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // projection for deuteranopia (M = 0) 101ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 lms2lmsd( 1.0000, -p0.x / p0.y, 0.0000, 0, 102ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.0000, 0.0000, 0.0000, 0, 103ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.0000, -p0.z / p0.y, 1.0000, 0, 104ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0 , 0 , 0 , 1); 105ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 106ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // projection for tritanopia (S = 0) 107ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 lms2lmst( 1.0000, 0.0000, -p1.x / p1.z, 0, 108ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.0000, 1.0000, -p1.y / p1.z, 0, 109ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.0000, 0.0000, 0.0000, 0, 110ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0 , 0 , 0 , 1); 111ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 112ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // We will calculate the error between the color and the color viewed by 113ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // a color blind user and "spread" this error onto the healthy cones. 114ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // The matrices below perform this last step and have been chosen arbitrarily. 115ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 116ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // The amount of correction can be adjusted here. 117ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 118ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // error spread for protanopia 119ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 errp( 1.0, 0.7, 0.7, 0, 120ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.0, 1.0, 0.0, 0, 121ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.0, 0.0, 1.0, 0, 122ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0, 0, 0, 1); 123ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 124ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // error spread for deuteranopia 125ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 errd( 1.0, 0.0, 0.0, 0, 126ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.7, 1.0, 0.7, 0, 127ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.0, 0.0, 1.0, 0, 128ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0, 0, 0, 1); 129ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 130ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // error spread for tritanopia 131ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 errt( 1.0, 0.0, 0.0, 0, 132ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.0, 1.0, 0.0, 0, 133ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0.7, 0.7, 1.0, 0, 134ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 0, 0, 0, 1); 135ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 136ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian const mat4 identity; 137ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 138ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // And the magic happens here... 139ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // We construct the matrix that will perform the whole correction. 140ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 141ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // simulation: type of color blindness to simulate: 142ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // set to either lms2lmsp, lms2lmsd, lms2lmst 143ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian mat4 simulation; 144ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 145ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // correction: type of color blindness correction (should match the simulation above): 146ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian // set to identity, errp, errd, errt ([0] for simulation only) 147ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian mat4 correction(0); 148ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 149ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian switch (mType) { 1509f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza case ColorBlindnessType::Protanomaly: 151ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian simulation = lms2lmsp; 1529f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza if (mMode == ColorBlindnessMode::Correction) 153ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian correction = errp; 154ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian break; 1559f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza case ColorBlindnessType::Deuteranomaly: 156ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian simulation = lms2lmsd; 1579f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza if (mMode == ColorBlindnessMode::Correction) 158ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian correction = errd; 159ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian break; 1609f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza case ColorBlindnessType::Tritanomaly: 161ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian simulation = lms2lmst; 1629f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza if (mMode == ColorBlindnessMode::Correction) 163ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian correction = errt; 164ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian break; 1659f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza case ColorBlindnessType::None: 1669f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza // We already caught this at the beginning of the method, but the 1679f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza // compiler doesn't know that 1689f26a9c8be6f00f55cbc30b93adf4895c6a093aaDan Stoza break; 169ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian } 170ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 171cd554e36a85e39556149ce4609da6e5fe0a6a176Alan Viverette mColorTransform = lms2rgb * 172cd554e36a85e39556149ce4609da6e5fe0a6a176Alan Viverette (simulation * rgb2lms + correction * (rgb2lms - simulation * rgb2lms)); 173ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian} 174ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian 175ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian} /* namespace android */ 176