Daltonizer.cpp 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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. #include "Daltonizer.h"
  17. #include <ui/mat4.h>
  18. namespace android {
  19. Daltonizer::Daltonizer() :
  20. mType(deuteranomaly), mMode(simulation), mDirty(true) {
  21. }
  22. Daltonizer::~Daltonizer() {
  23. }
  24. void Daltonizer::setType(Daltonizer::ColorBlindnessTypes type) {
  25. if (type != mType) {
  26. mDirty = true;
  27. mType = type;
  28. }
  29. }
  30. void Daltonizer::setMode(Daltonizer::Mode mode) {
  31. if (mode != mMode) {
  32. mDirty = true;
  33. mMode = mode;
  34. }
  35. }
  36. const mat4& Daltonizer::operator()() {
  37. if (mDirty) {
  38. mDirty = false;
  39. update();
  40. }
  41. return mColorTransform;
  42. }
  43. void Daltonizer::update() {
  44. // converts a linear RGB color to the XYZ space
  45. const mat4 rgb2xyz( 0.4124, 0.2126, 0.0193, 0,
  46. 0.3576, 0.7152, 0.1192, 0,
  47. 0.1805, 0.0722, 0.9505, 0,
  48. 0 , 0 , 0 , 1);
  49. // converts a XYZ color to the LMS space.
  50. const mat4 xyz2lms( 0.7328,-0.7036, 0.0030, 0,
  51. 0.4296, 1.6975, 0.0136, 0,
  52. -0.1624, 0.0061, 0.9834, 0,
  53. 0 , 0 , 0 , 1);
  54. // Direct conversion from linear RGB to LMS
  55. const mat4 rgb2lms(xyz2lms*rgb2xyz);
  56. // And back from LMS to linear RGB
  57. const mat4 lms2rgb(inverse(rgb2lms));
  58. // To simulate color blindness we need to "remove" the data lost by the absence of
  59. // a cone. This cannot be done by just zeroing out the corresponding LMS component
  60. // because it would create a color outside of the RGB gammut.
  61. // Instead we project the color along the axis of the missing component onto a plane
  62. // within the RGB gammut:
  63. // - since the projection happens along the axis of the missing component, a
  64. // color blind viewer perceives the projected color the same.
  65. // - We use the plane defined by 3 points in LMS space: black, white and
  66. // blue and red for protanopia/deuteranopia and tritanopia respectively.
  67. // LMS space red
  68. const vec3& lms_r(rgb2lms[0].rgb);
  69. // LMS space blue
  70. const vec3& lms_b(rgb2lms[2].rgb);
  71. // LMS space white
  72. const vec3 lms_w((rgb2lms * vec4(1)).rgb);
  73. // To find the planes we solve the a*L + b*M + c*S = 0 equation for the LMS values
  74. // of the three known points. This equation is trivially solved, and has for
  75. // solution the following cross-products:
  76. const vec3 p0 = cross(lms_w, lms_b); // protanopia/deuteranopia
  77. const vec3 p1 = cross(lms_w, lms_r); // tritanopia
  78. // The following 3 matrices perform the projection of a LMS color onto the given plane
  79. // along the selected axis
  80. // projection for protanopia (L = 0)
  81. const mat4 lms2lmsp( 0.0000, 0.0000, 0.0000, 0,
  82. -p0.y / p0.x, 1.0000, 0.0000, 0,
  83. -p0.z / p0.x, 0.0000, 1.0000, 0,
  84. 0 , 0 , 0 , 1);
  85. // projection for deuteranopia (M = 0)
  86. const mat4 lms2lmsd( 1.0000, -p0.x / p0.y, 0.0000, 0,
  87. 0.0000, 0.0000, 0.0000, 0,
  88. 0.0000, -p0.z / p0.y, 1.0000, 0,
  89. 0 , 0 , 0 , 1);
  90. // projection for tritanopia (S = 0)
  91. const mat4 lms2lmst( 1.0000, 0.0000, -p1.x / p1.z, 0,
  92. 0.0000, 1.0000, -p1.y / p1.z, 0,
  93. 0.0000, 0.0000, 0.0000, 0,
  94. 0 , 0 , 0 , 1);
  95. // We will calculate the error between the color and the color viewed by
  96. // a color blind user and "spread" this error onto the healthy cones.
  97. // The matrices below perform this last step and have been chosen arbitrarily.
  98. // The amount of correction can be adjusted here.
  99. // error spread for protanopia
  100. const mat4 errp( 1.0, 0.7, 0.7, 0,
  101. 0.0, 1.0, 0.0, 0,
  102. 0.0, 0.0, 1.0, 0,
  103. 0, 0, 0, 1);
  104. // error spread for deuteranopia
  105. const mat4 errd( 1.0, 0.0, 0.0, 0,
  106. 0.7, 1.0, 0.7, 0,
  107. 0.0, 0.0, 1.0, 0,
  108. 0, 0, 0, 1);
  109. // error spread for tritanopia
  110. const mat4 errt( 1.0, 0.0, 0.0, 0,
  111. 0.0, 1.0, 0.0, 0,
  112. 0.7, 0.7, 1.0, 0,
  113. 0, 0, 0, 1);
  114. const mat4 identity;
  115. // And the magic happens here...
  116. // We construct the matrix that will perform the whole correction.
  117. // simulation: type of color blindness to simulate:
  118. // set to either lms2lmsp, lms2lmsd, lms2lmst
  119. mat4 simulation;
  120. // correction: type of color blindness correction (should match the simulation above):
  121. // set to identity, errp, errd, errt ([0] for simulation only)
  122. mat4 correction(0);
  123. switch (mType) {
  124. case protanopia:
  125. case protanomaly:
  126. simulation = lms2lmsp;
  127. if (mMode == Daltonizer::correction)
  128. correction = errp;
  129. break;
  130. case deuteranopia:
  131. case deuteranomaly:
  132. simulation = lms2lmsd;
  133. if (mMode == Daltonizer::correction)
  134. correction = errd;
  135. break;
  136. case tritanopia:
  137. case tritanomaly:
  138. simulation = lms2lmst;
  139. if (mMode == Daltonizer::correction)
  140. correction = errt;
  141. break;
  142. }
  143. mColorTransform = lms2rgb *
  144. (simulation * rgb2lms + correction * (rgb2lms - simulation * rgb2lms));
  145. }
  146. } /* namespace android */