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 177 178 179 180
|
/*
* 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.
*/
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
#include "Daltonizer.h"
#include <math/mat4.h>
namespace android {
void Daltonizer::setType(ColorBlindnessType type) {
if (type != mType) {
mDirty = true;
mType = type;
}
}
void Daltonizer::setMode(ColorBlindnessMode mode) {
if (mode != mMode) {
mDirty = true;
mMode = mode;
}
}
const mat4& Daltonizer::operator()() {
if (mDirty) {
mDirty = false;
update();
}
return mColorTransform;
}
void Daltonizer::update() {
if (mType == ColorBlindnessType::None) {
mColorTransform = mat4();
return;
}
// 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);
// 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 ColorBlindnessType::Protanomaly:
simulation = lms2lmsp;
if (mMode == ColorBlindnessMode::Correction)
correction = errp;
break;
case ColorBlindnessType::Deuteranomaly:
simulation = lms2lmsd;
if (mMode == ColorBlindnessMode::Correction)
correction = errd;
break;
case ColorBlindnessType::Tritanomaly:
simulation = lms2lmst;
if (mMode == ColorBlindnessMode::Correction)
correction = errt;
break;
case ColorBlindnessType::None:
// We already caught this at the beginning of the method, but the
// compiler doesn't know that
break;
}
mColorTransform = lms2rgb *
(simulation * rgb2lms + correction * (rgb2lms - simulation * rgb2lms));
}
} /* namespace android */
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic pop // ignored "-Wconversion"
|