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;