File indexing completed on 2024-04-28 03:40:27
0001 // SPDX-FileCopyrightText: 2008 by Matthew Woehlke <mw_triad@users.sourceforge.net> 0002 // SPDX-License-Identifier: GPL-2.0-or-later 0003 0004 /*************************************************************************** 0005 0006 Citations: 0007 0008 [1] H. Brettel, F. Viénot and J. D. Mollon (1997) 0009 "Computerized simulation of color appearance for dichromats." 0010 J. Opt. Soc. Am. A 14, 2647-2655. 0011 [2] F. Viénot, H. Brettel and J. D. Mollon (1999) 0012 "Digital video colourmaps for checking the legibility of displays by 0013 dichromats." 0014 Color Research and Application 24, 243-252. 0015 0016 ***************************************************************************/ 0017 0018 // application specific includes 0019 #include "colorsim.h" 0020 0021 // include files for Qt 0022 #include <QColor> 0023 0024 #include <math.h> 0025 0026 typedef qreal matrix[3][3]; 0027 0028 #define SIMPLE_ALGORITHM 0029 0030 #ifndef SIMPLE_ALGORITHM 0031 typedef qreal vector[3]; 0032 0033 struct fcoef { 0034 vector k[2]; 0035 // vector e; 0036 matrix s[2]; 0037 }; 0038 #endif 0039 0040 class xyza { 0041 private: 0042 qreal x, y, z, a; 0043 0044 public: 0045 xyza() {} 0046 xyza(const QColor &c); 0047 QRgb rgba() const; 0048 xyza gamma(qreal q) const; 0049 xyza operator*(const matrix m) const; 0050 #ifndef SIMPLE_ALGORITHM 0051 xyza(const vector v); 0052 qreal operator*(const vector m) const; 0053 xyza flatten(fcoef c) const; 0054 #endif 0055 }; 0056 0057 xyza::xyza(const QColor &c) : 0058 x(c.redF()), y(c.greenF()), z(c.blueF()), a(c.alphaF()) 0059 { 0060 } 0061 0062 inline qreal clamp(qreal n) 0063 { 0064 return qMin(qreal(1.0), qMax(qreal(0.0), n)); 0065 } 0066 0067 QRgb xyza::rgba() const 0068 { 0069 return QColor::fromRgbF(clamp(x), clamp(y), clamp(z), a).rgba(); 0070 } 0071 0072 xyza xyza::operator*(const matrix m) const 0073 { 0074 xyza r; 0075 r.x = (x * m[0][0]) + (y * m[0][1]) + (z * m[0][2]); 0076 r.y = (x * m[1][0]) + (y * m[1][1]) + (z * m[1][2]); 0077 r.z = (x * m[2][0]) + (y * m[2][1]) + (z * m[2][2]); 0078 r.a = a; 0079 return r; 0080 } 0081 0082 xyza xyza::gamma(qreal q) const 0083 { 0084 xyza r; 0085 r.x = pow(x, q); 0086 r.y = pow(y, q); 0087 r.z = pow(z, q); 0088 r.a = a; 0089 return r; 0090 } 0091 0092 #if !defined(SIMPLE_ALGORITHM) || !defined(STANFORD_ALGORITHM) 0093 0094 /*************************************************************************** 0095 0096 These RGB<->LMS transformation matrices are from [1]. 0097 0098 ***************************************************************************/ 0099 0100 static const matrix rgb2lms = { 0101 {0.1992, 0.4112, 0.0742}, 0102 {0.0353, 0.2226, 0.0574}, 0103 {0.0185, 0.1231, 1.3550} 0104 }; 0105 0106 static const matrix lms2rgb = { 0107 { 7.4645, -13.8882, 0.1796}, 0108 {-1.1852, 6.8053, -0.2234}, 0109 { 0.0058, -0.4286, 0.7558} 0110 }; 0111 0112 #endif 0113 0114 #ifdef SIMPLE_ALGORITHM 0115 0116 # ifndef STANFORD_ALGORITHM // from "Computerized simulation of color appearance for dichromats" 0117 0118 # ifdef CRA_ALGORITHM 0119 0120 /*************************************************************************** 0121 0122 These matrices are derived from Table II in [2], for the code based on the 0123 Onur/Poliang algorithm below, using the LMS transformation from [1]. 0124 No tritanopia data were provided, so that simulation does not work correctly. 0125 0126 ***************************************************************************/ 0127 0128 static const matrix coef[3] = { 0129 { {0.0, 2.39238646, -0.04658523}, {0.0, 1.0, 0.0 }, {0.0, 0.0, 1.0} }, 0130 { {1.0, 0.0, 0.0 }, {0.41799267, 0.0, 0.01947228}, {0.0, 0.0, 1.0} }, 0131 { {1.0, 0.0, 0.0 }, {0.0, 1.0, 0.0 }, {0.0, 0.0, 0.0} } 0132 }; 0133 0134 # else 0135 0136 /*************************************************************************** 0137 0138 These matrices are derived from the "Color Blindness Simulation" sample 0139 palettes from Visolve, Ryobi System Solutions, using the LMS transformations 0140 from [1]. 0141 https://www.ryobi-sol.co.jp/visolve/en/simulation.html 0142 0143 ***************************************************************************/ 0144 0145 static const matrix coef[3] = { 0146 { {0.0, 2.60493696, -0.08742194}, {0.0, 1.0, 0.0 }, {0.0, 0.0, 1.0} }, 0147 { {1.0, 0.0, 0.0 }, {0.38395980, 0.0, 0.03370622}, {0.0, 0.0, 1.0} }, 0148 { {1.0, 0.0, 0.0 }, {0.0, 1.0, 0.0 }, {-3.11932916, 12.18076308, 0.0} } 0149 }; 0150 0151 # endif 0152 0153 # else // from the "Analysis of Color Blindness" project 0154 0155 /*************************************************************************** 0156 0157 This code is based on the matrices from [2], as presented by Onur and Poliang. 0158 The tritanopia simulation uses different representative wavelengths (yellow 0159 and blue) than those recommended by [1] and found in most other simulations 0160 (red and cyan). 0161 https://stacks.stanford.edu/file/druid:yj296hj2790/Woods_Assisting_Color_Blind_Viewers.pdf 0162 0163 ***************************************************************************/ 0164 0165 static const matrix coef[3] = { 0166 { { 0.0, 2.02344, -2.52581}, {0.0, 1.0, 0.0 }, { 0.0, 0.0, 1.0} }, 0167 { { 1.0, 0.0, 0.0 }, {0.494207, 0.0, 1.24827}, { 0.0, 0.0, 1.0} }, 0168 { { 1.0, 0.0, 0.0 }, {0.0, 1.0, 0.0 }, {-0.395913, 0.801109, 0.0} } 0169 }; 0170 0171 static const matrix rgb2lms = { 0172 {17.8824, 43.5161, 4.11935}, 0173 { 3.45565, 27.1554, 3.86714}, 0174 { 0.0299566, 0.184309, 1.46709} 0175 }; 0176 0177 static const matrix lms2rgb = { 0178 { 0.080944447905, -0.130504409160, 0.116721066440}, 0179 {-0.010248533515, 0.054019326636, -0.113614708214}, 0180 {-0.000365296938, -0.004121614686, 0.693511404861} 0181 }; 0182 0183 # endif 0184 0185 inline QRgb recolor(QRgb c, int mode, qreal g) 0186 { 0187 if (mode > 0 && mode < 4) { 0188 xyza n = QColor(c); 0189 if (g != 1.0) { 0190 xyza r = n.gamma(g) * rgb2lms * coef[mode-1] * lms2rgb; 0191 return r.gamma(qreal(1.0) / g).rgba(); 0192 } 0193 else { 0194 xyza r = n * rgb2lms * coef[mode-1] * lms2rgb; 0195 return r.rgba(); 0196 } 0197 } 0198 else { 0199 return qRgb(qGray(c), qGray(c), qGray(c)); 0200 } 0201 } 0202 0203 #else // from "Computerized simulation of color appearance for dichromats" 0204 0205 /*************************************************************************** 0206 0207 This code is based on [1]. The RGB<->LMS transformation matrices are declared 0208 above. 0209 0210 ***************************************************************************/ 0211 0212 static const fcoef coef[3] = { 0213 { 0214 { {0.0, 0.0, 1.0}, {0.0, 1.0, 0.0} }, // k 0215 // { }, // e 0216 // a { } 0217 { // s 0218 { {0.0, 2.39238646, -0.04658523}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0} }, 0219 { {0.0, 0.37421464, -0.02034378}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0} } 0220 } 0221 }, 0222 { 0223 { {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0} }, // k 0224 // { }, // e 0225 // a { } 0226 { // s 0227 { {1.0, 0.0, 0.0}, {0.41799267, 0.0, 0.01947228}, {0.0, 0.0, 1.0} }, 0228 { {1.0, 0.0, 0.0}, {0.41799267, 0.0, 0.01947228}, {0.0, 0.0, 1.0} } 0229 } 0230 }, 0231 { 0232 { {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0} }, // k 0233 // { }, // e 0234 // a { } 0235 { // s 0236 { {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 0.0} }, 0237 { {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 0.0} } 0238 } 0239 } 0240 /* The 's' matrices are calculated thusly: 0241 u = E.y*A.z - E.z*A.y; 0242 v = E.z*A.x - E.x*A.z; 0243 w = E.x*A.y - E.y*A.x; 0244 r.x = (mode != 1) ? x : (-v/u) * y + (-w/u) * z; 0245 r.y = (mode != 2) ? y : (-u/v) * x + (-w/v) * z; 0246 r.z = (mode != 3) ? z : (-u/w) * x + (-v/w) * y; 0247 */ 0248 }; 0249 0250 xyza::xyza(const vector v) : 0251 x(v[0]), y(v[1]), z(v[2]), a(0.0) 0252 { 0253 } 0254 0255 qreal xyza::operator*(const vector v) const 0256 { 0257 return (x * v[0]) + (y * v[1]) + (z * v[2]); 0258 } 0259 0260 xyza xyza::flatten(fcoef c) const 0261 { 0262 // xyza e(c.e); 0263 // qreal u = (*this * c.k[0]) / (*this * c.k[1]); 0264 // qreal v = (e * c.k[0]) / (e * c.k[1]); 0265 // int i = (u < v ? 0 : 1); 0266 int i = 0; 0267 return *this * c.s[i]; 0268 } 0269 0270 inline QRgb recolor(QRgb c, int mode, qreal g) 0271 { 0272 if (mode > 0 && mode < 4) { 0273 xyza n = QColor(c); 0274 xyza r = n.gamma(g) * rgb2lms; 0275 r = r.flatten(coef[mode-1]) * lms2rgb; 0276 return r.gamma(qreal(1.0) / g).rgba(); 0277 } 0278 else { 0279 const int g = qGray(c); 0280 return qRgb(g,g,g); 0281 } 0282 } 0283 0284 #endif 0285 0286 QImage ColorSim::recolor(const QImage &pm, int mode, qreal gamma) 0287 { 0288 // get raw data in a format we can manipulate 0289 QImage i = pm; 0290 if (i.format() != QImage::Format_RGB32 && i.format() != QImage::Format_ARGB32) 0291 i = i.convertToFormat(QImage::Format_ARGB32); 0292 0293 int n = i.width() * i.height(); 0294 QRgb *d = (QRgb*)i.bits(); 0295 for (int k = 0; k < n; ++k) 0296 d[k] = ::recolor(d[k], mode, gamma); 0297 0298 return i; 0299 } 0300 // kate: indent-width 2;