replicant-frameworks_native/services/surfaceflinger/Effects/Daltonizer.cpp
Mathias Agopian ff2ed70fa3 color blindness enhancement
This is an attempt at improving the experience of
users with color vision impairement.

At this time this feature can only be enabled for
debugging:

  adb shell service call SurfaceFlinger 1014 i32 PARAM

  with PARAM:
   0 : disabled
   1 : protanomaly/protanopia simulation
   2 : deuteranomaly/deuteranopia simulation
   3 : tritanopia/tritanomaly simulation
  11, 12, 13: same as above w/ attempted correction/enhancement

The enhancement algorithm tries to spread the "error"
such that tones that would otherwise appear similar can be
distinguished.

Bug: 9465644

Change-Id: I860f7eed0cb81f54ef9cf24ad78155b6395ade48
2013-09-04 22:11:15 -07:00

184 lines
6.2 KiB
C++

/*
* 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);
// control: simulation post-correction (used for debugging):
// set to identity or lms2lmsp, lms2lmsd, lms2lmst
mat4 control;
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;
}
if (true) {
control = simulation;
}
mColorTransform = lms2rgb * control *
(simulation * rgb2lms + correction * (rgb2lms - simulation * rgb2lms));
}
} /* namespace android */