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