Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ff2ed70f authored by Mathias Agopian's avatar Mathias Agopian
Browse files

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
parent 1d4d8f94
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ LOCAL_SRC_FILES:= \
    DisplayHardware/HWComposer.cpp \
    DisplayHardware/PowerHAL.cpp \
    DisplayHardware/VirtualDisplaySurface.cpp \
    Effects/Daltonizer.cpp \
    EventLog/EventLogTags.logtags \
    EventLog/EventLog.cpp \
    RenderEngine/Description.cpp \
+183 −0
Original line number Diff line number Diff line
/*
 * 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 */
+59 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#ifndef SF_EFFECTS_DALTONIZER_H_
#define SF_EFFECTS_DALTONIZER_H_

#include <ui/mat4.h>

namespace android {

class Daltonizer {
public:
    enum ColorBlindnessTypes {
        protanopia,         // L (red) cone missing
        deuteranopia,       // M (green) cone missing
        tritanopia,         // S (blue) cone missing
        protanomaly,        // L (red) cone deficient
        deuteranomaly,      // M (green) cone deficient (most common)
        tritanomaly         // S (blue) cone deficient
    };

    enum Mode {
        simulation,
        correction
    };

    Daltonizer();
    ~Daltonizer();

    void setType(ColorBlindnessTypes type);
    void setMode(Mode mode);

    // returns the color transform to apply in the shader
    const mat4& operator()();

private:
    void update();

    ColorBlindnessTypes mType;
    Mode mMode;
    bool mDirty;
    mat4 mColorTransform;
};

} /* namespace android */
#endif /* SF_EFFECTS_DALTONIZER_H_ */
+10 −14
Original line number Diff line number Diff line
@@ -547,15 +547,11 @@ void Layer::drawWithOpenGL(

    // TODO: we probably want to generate the texture coords with the mesh
    // here we assume that we only have 4 vertices
    Mesh::VertexArray texCoords(mMesh.getTexCoordArray());
    texCoords[0].s = left;
    texCoords[0].t = 1.0f - top;
    texCoords[1].s = left;
    texCoords[1].t = 1.0f - bottom;
    texCoords[2].s = right;
    texCoords[2].t = 1.0f - bottom;
    texCoords[3].s = right;
    texCoords[3].t = 1.0f - top;
    Mesh::VertexArray<vec2> texCoords(mMesh.getTexCoordArray<vec2>());
    texCoords[0] = vec2(left, 1.0f - top);
    texCoords[1] = vec2(left, 1.0f - bottom);
    texCoords[2] = vec2(right, 1.0f - bottom);
    texCoords[3] = vec2(right, 1.0f - top);

    RenderEngine& engine(mFlinger->getRenderEngine());
    engine.setupLayerBlending(mPremultipliedAlpha, isOpaque(), s.alpha);
@@ -608,11 +604,11 @@ void Layer::computeGeometry(const sp<const DisplayDevice>& hw, Mesh& mesh) const
    // subtract the transparent region and snap to the bounds
    win = reduce(win, s.activeTransparentRegion);

    Mesh::VertexArray position(mesh.getPositionArray());
    tr.transform(position[0], win.left,  win.top);
    tr.transform(position[1], win.left,  win.bottom);
    tr.transform(position[2], win.right, win.bottom);
    tr.transform(position[3], win.right, win.top);
    Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
    position[0] = tr.transform(win.left,  win.top);
    position[1] = tr.transform(win.left,  win.bottom);
    position[2] = tr.transform(win.right, win.bottom);
    position[3] = tr.transform(win.right, win.top);
    for (size_t i=0 ; i<4 ; i++) {
        position[i].y = hw_h - position[i].y;
    }
+9 −1
Original line number Diff line number Diff line
@@ -29,9 +29,10 @@ namespace android {
Description::Description() :
    mUniformsDirty(true) {
    mPlaneAlpha = 1.0f;
    mPremultipliedAlpha = true;
    mPremultipliedAlpha = false;
    mOpaque = true;
    mTextureEnabled = false;
    mColorMatrixEnabled = false;

    memset(mColor, 0, sizeof(mColor));
}
@@ -81,4 +82,11 @@ void Description::setProjectionMatrix(const mat4& mtx) {
    mUniformsDirty = true;
}

void Description::setColorMatrix(const mat4& mtx) {
    const mat4 identity;
    mColorMatrix = mtx;
    mColorMatrixEnabled = (mtx != identity);
}


} /* namespace android */
Loading