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