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