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
22ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias AgopianDaltonizer::Daltonizer() :
23ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    mType(deuteranomaly), mMode(simulation), mDirty(true) {
24ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian}
25ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
26ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias AgopianDaltonizer::~Daltonizer() {
27ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian}
28ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
29ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopianvoid Daltonizer::setType(Daltonizer::ColorBlindnessTypes type) {
30ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    if (type != mType) {
31ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        mDirty = true;
32ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        mType = type;
33ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    }
34ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian}
35ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
36ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopianvoid Daltonizer::setMode(Daltonizer::Mode mode) {
37ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    if (mode != mMode) {
38ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        mDirty = true;
39ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        mMode = mode;
40ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    }
41ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian}
42ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
43ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopianconst mat4& Daltonizer::operator()() {
44ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    if (mDirty) {
45ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        mDirty = false;
46ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        update();
47ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    }
48ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    return mColorTransform;
49ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian}
50ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
51ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopianvoid Daltonizer::update() {
52ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // converts a linear RGB color to the XYZ space
53ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 rgb2xyz( 0.4124, 0.2126, 0.0193, 0,
54ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0.3576, 0.7152, 0.1192, 0,
55ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0.1805, 0.0722, 0.9505, 0,
56ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0     , 0     , 0     , 1);
57ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
58ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // converts a XYZ color to the LMS space.
59ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 xyz2lms( 0.7328,-0.7036, 0.0030, 0,
60ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0.4296, 1.6975, 0.0136, 0,
61ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                       -0.1624, 0.0061, 0.9834, 0,
62ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0     , 0     , 0     , 1);
63ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
64ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // Direct conversion from linear RGB to LMS
65ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 rgb2lms(xyz2lms*rgb2xyz);
66ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
67ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // And back from LMS to linear RGB
68ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 lms2rgb(inverse(rgb2lms));
69ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
70ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // To simulate color blindness we need to "remove" the data lost by the absence of
71ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // a cone. This cannot be done by just zeroing out the corresponding LMS component
72ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // because it would create a color outside of the RGB gammut.
73ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // Instead we project the color along the axis of the missing component onto a plane
74ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // within the RGB gammut:
75ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    //  - since the projection happens along the axis of the missing component, a
76ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    //    color blind viewer perceives the projected color the same.
77ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    //  - We use the plane defined by 3 points in LMS space: black, white and
78ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    //    blue and red for protanopia/deuteranopia and tritanopia respectively.
79ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
80ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // LMS space red
81ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const vec3& lms_r(rgb2lms[0].rgb);
82ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // LMS space blue
83ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const vec3& lms_b(rgb2lms[2].rgb);
84ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // LMS space white
85ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const vec3 lms_w((rgb2lms * vec4(1)).rgb);
86ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
87ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // To find the planes we solve the a*L + b*M + c*S = 0 equation for the LMS values
88ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // of the three known points. This equation is trivially solved, and has for
89ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // solution the following cross-products:
90ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const vec3 p0 = cross(lms_w, lms_b);    // protanopia/deuteranopia
91ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const vec3 p1 = cross(lms_w, lms_r);    // tritanopia
92ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
93ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // The following 3 matrices perform the projection of a LMS color onto the given plane
94ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // along the selected axis
95ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
96ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // projection for protanopia (L = 0)
97ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 lms2lmsp(  0.0000, 0.0000, 0.0000, 0,
98ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                    -p0.y / p0.x, 1.0000, 0.0000, 0,
99ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                    -p0.z / p0.x, 0.0000, 1.0000, 0,
100ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0     , 0     , 0     , 1);
101ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
102ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // projection for deuteranopia (M = 0)
103ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 lms2lmsd(  1.0000, -p0.x / p0.y, 0.0000, 0,
104ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0.0000,       0.0000, 0.0000, 0,
105ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0.0000, -p0.z / p0.y, 1.0000, 0,
106ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0     ,       0     , 0     , 1);
107ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
108ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // projection for tritanopia (S = 0)
109ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 lms2lmst(  1.0000, 0.0000, -p1.x / p1.z, 0,
110ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0.0000, 1.0000, -p1.y / p1.z, 0,
111ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0.0000, 0.0000,       0.0000, 0,
112ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0     ,       0     , 0     , 1);
113ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
114ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // We will calculate the error between the color and the color viewed by
115ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // a color blind user and "spread" this error onto the healthy cones.
116ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // The matrices below perform this last step and have been chosen arbitrarily.
117ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
118ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // The amount of correction can be adjusted here.
119ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
120ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // error spread for protanopia
121ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 errp(    1.0, 0.7, 0.7, 0,
122ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0.0, 1.0, 0.0, 0,
123ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0.0, 0.0, 1.0, 0,
124ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0,   0,   0, 1);
125ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
126ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // error spread for deuteranopia
127ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 errd(    1.0, 0.0, 0.0, 0,
128ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0.7, 1.0, 0.7, 0,
129ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0.0, 0.0, 1.0, 0,
130ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0,   0,   0, 1);
131ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
132ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // error spread for tritanopia
133ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 errt(    1.0, 0.0, 0.0, 0,
134ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0.0, 1.0, 0.0, 0,
135ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                        0.7, 0.7, 1.0, 0,
136ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                          0,   0,   0, 1);
137ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
138ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    const mat4 identity;
139ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
140ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // And the magic happens here...
141ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // We construct the matrix that will perform the whole correction.
142ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
143ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // simulation: type of color blindness to simulate:
144ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // set to either lms2lmsp, lms2lmsd, lms2lmst
145ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    mat4 simulation;
146ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
147ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // correction: type of color blindness correction (should match the simulation above):
148ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    // set to identity, errp, errd, errt ([0] for simulation only)
149ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    mat4 correction(0);
150ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
151ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    switch (mType) {
152ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        case protanopia:
153ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        case protanomaly:
154ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian            simulation = lms2lmsp;
155ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian            if (mMode == Daltonizer::correction)
156ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                correction = errp;
157ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian            break;
158ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        case deuteranopia:
159ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        case deuteranomaly:
160ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian            simulation = lms2lmsd;
161ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian            if (mMode == Daltonizer::correction)
162ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                correction = errd;
163ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian            break;
164ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        case tritanopia:
165ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian        case tritanomaly:
166ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian            simulation = lms2lmst;
167ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian            if (mMode == Daltonizer::correction)
168ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian                correction = errt;
169ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian            break;
170ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian    }
171ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
172cd554e36a85e39556149ce4609da6e5fe0a6a176Alan Viverette    mColorTransform = lms2rgb *
173cd554e36a85e39556149ce4609da6e5fe0a6a176Alan Viverette        (simulation * rgb2lms + correction * (rgb2lms - simulation * rgb2lms));
174ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian}
175ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian
176ff2ed70fa30f04b90dd1a2c06ec2319e157152d7Mathias Agopian} /* namespace android */
177