1 /***************************************************************************
2 colorsim.cpp - description
4 begin : Mon Jan 21 14:54:37 CST 2008
5 copyright : (C) 2008 by Matthew Woehlke
6 email : mw_triad@users.sourceforge.net
7 ***************************************************************************/
9 /***************************************************************************
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
16 ***************************************************************************/
18 /***************************************************************************
22 [1] H. Brettel, F. Viénot and J. D. Mollon (1997)
23 "Computerized simulation of color appearance for dichromats."
24 J. Opt. Soc. Am. A 14, 2647-2655.
25 [2] F. Viénot, H. Brettel and J. D. Mollon (1999)
26 "Digital video colourmaps for checking the legibility of displays by
28 Color Research and Application 24, 243-252.
30 ***************************************************************************/
32 // application specific includes
35 // include files for Qt
36 #include <QtGui/QColor>
40 typedef qreal matrix
[3][3];
42 #define SIMPLE_ALGORITHM
44 #ifndef SIMPLE_ALGORITHM
45 typedef qreal vector
[3];
60 xyza(const QColor
&c
);
62 xyza
gamma(qreal q
) const;
63 xyza
operator*(const matrix m
) const;
64 #ifndef SIMPLE_ALGORITHM
66 qreal
operator*(const vector m
) const;
67 xyza
flatten(fcoef c
) const;
71 xyza::xyza(const QColor
&c
) :
72 x(c
.redF()), y(c
.greenF()), z(c
.blueF()), a(c
.alphaF())
76 inline qreal
clamp(qreal n
)
78 return qMin(qreal(1.0), qMax(qreal(0.0), n
));
81 QRgb
xyza::rgba() const
83 return QColor::fromRgbF(clamp(x
), clamp(y
), clamp(z
), a
).rgba();
86 xyza
xyza::operator*(const matrix m
) const
89 r
.x
= (x
* m
[0][0]) + (y
* m
[0][1]) + (z
* m
[0][2]);
90 r
.y
= (x
* m
[1][0]) + (y
* m
[1][1]) + (z
* m
[1][2]);
91 r
.z
= (x
* m
[2][0]) + (y
* m
[2][1]) + (z
* m
[2][2]);
96 xyza
xyza::gamma(qreal q
) const
106 #if !defined(SIMPLE_ALGORITHM) || !defined(STANFORD_ALGORITHM)
108 /***************************************************************************
110 These RGB<->LMS transformation matrices are from [1].
112 ***************************************************************************/
114 static const matrix rgb2lms
= {
115 {0.1992, 0.4112, 0.0742},
116 {0.0353, 0.2226, 0.0574},
117 {0.0185, 0.1231, 1.3550}
120 static const matrix lms2rgb
= {
121 { 7.4645, -13.8882, 0.1796},
122 {-1.1852, 6.8053, -0.2234},
123 { 0.0058, -0.4286, 0.7558}
128 #ifdef SIMPLE_ALGORITHM
130 # ifndef STANFORD_ALGORITHM // from "Computerized simulation of color appearance for dichromats"
132 # ifdef CRA_ALGORITHM
134 /***************************************************************************
136 These matrices are derived from Table II in [2], for the code based on the
137 Onur/Poliang algorithm below, using the LMS transformation from [1].
138 No tritanopia data were provided, so that simulation does not work correctly.
140 ***************************************************************************/
142 static const matrix coef
[3] = {
143 { {0.0, 2.39238646, -0.04658523}, {0.0, 1.0, 0.0 }, {0.0, 0.0, 1.0} },
144 { {1.0, 0.0, 0.0 }, {0.41799267, 0.0, 0.01947228}, {0.0, 0.0, 1.0} },
145 { {1.0, 0.0, 0.0 }, {0.0, 1.0, 0.0 }, {0.0, 0.0, 0.0} }
150 /***************************************************************************
152 These matrices are derived from the "Color Blindness Simulation" sample
153 palettes from Visolve, Ryobi System Solutions, using the LMS transformations
155 http://www.ryobi-sol.co.jp/visolve/en/simulation.html
157 ***************************************************************************/
159 static const matrix coef
[3] = {
160 { {0.0, 2.60493696, -0.08742194}, {0.0, 1.0, 0.0 }, {0.0, 0.0, 1.0} },
161 { {1.0, 0.0, 0.0 }, {0.38395980, 0.0, 0.03370622}, {0.0, 0.0, 1.0} },
162 { {1.0, 0.0, 0.0 }, {0.0, 1.0, 0.0 }, {-3.11932916, 12.18076308, 0.0} }
167 # else // from the "Analysis of Color Blindness" project
169 /***************************************************************************
171 This code is based on the matrices from [2], as presented by Onur and Poliang.
172 The tritanopia simulation uses different representative wavelengths (yellow
173 and blue) than those reccommended by [1] and found in most other simulations
175 http://www.stanford.edu/~ofidaner/psych221_proj/colorblindness_project.htm
177 ***************************************************************************/
179 static const matrix coef
[3] = {
180 { { 0.0, 2.02344, -2.52581}, {0.0, 1.0, 0.0 }, { 0.0, 0.0, 1.0} },
181 { { 1.0, 0.0, 0.0 }, {0.494207, 0.0, 1.24827}, { 0.0, 0.0, 1.0} },
182 { { 1.0, 0.0, 0.0 }, {0.0, 1.0, 0.0 }, {-0.395913, 0.801109, 0.0} }
185 static const matrix rgb2lms
= {
186 {17.8824, 43.5161, 4.11935},
187 { 3.45565, 27.1554, 3.86714},
188 { 0.0299566, 0.184309, 1.46709}
191 static const matrix lms2rgb
= {
192 { 0.080944447905, -0.130504409160, 0.116721066440},
193 {-0.010248533515, 0.054019326636, -0.113614708214},
194 {-0.000365296938, -0.004121614686, 0.693511404861}
199 inline QRgb
recolor(QRgb c
, int mode
, qreal g
)
201 if (mode
> 0 && mode
< 4) {
204 xyza r
= n
.gamma(g
) * rgb2lms
* coef
[mode
-1] * lms2rgb
;
205 return r
.gamma(qreal(1.0) / g
).rgba();
208 xyza r
= n
* rgb2lms
* coef
[mode
-1] * lms2rgb
;
213 return qRgb(qGray(c
), qGray(c
), qGray(c
));
217 #else // from "Computerized simulation of color appearance for dichromats"
219 /***************************************************************************
221 This code is based on [1]. The RGB<->LMS transformation matrices are declared
224 ***************************************************************************/
226 static const fcoef coef
[3] = {
228 { {0.0, 0.0, 1.0}, {0.0, 1.0, 0.0} }, // k
232 { {0.0, 2.39238646, -0.04658523}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0} },
233 { {0.0, 0.37421464, -0.02034378}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0} }
237 { {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0} }, // k
241 { {1.0, 0.0, 0.0}, {0.41799267, 0.0, 0.01947228}, {0.0, 0.0, 1.0} },
242 { {1.0, 0.0, 0.0}, {0.41799267, 0.0, 0.01947228}, {0.0, 0.0, 1.0} }
246 { {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0} }, // k
250 { {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 0.0} },
251 { {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 0.0} }
254 /* The 's' matrices are calculated thusly:
255 u = E.y*A.z - E.z*A.y;
256 v = E.z*A.x - E.x*A.z;
257 w = E.x*A.y - E.y*A.x;
258 r.x = (mode != 1) ? x : (-v/u) * y + (-w/u) * z;
259 r.y = (mode != 2) ? y : (-u/v) * x + (-w/v) * z;
260 r.z = (mode != 3) ? z : (-u/w) * x + (-v/w) * y;
264 xyza::xyza(const vector v
) :
265 x(v
[0]), y(v
[1]), z(v
[2]), a(0.0)
269 qreal
xyza::operator*(const vector v
) const
271 return (x
* v
[0]) + (y
* v
[1]) + (z
* v
[2]);
274 xyza
xyza::flatten(fcoef c
) const
277 // qreal u = (*this * c.k[0]) / (*this * c.k[1]);
278 // qreal v = (e * c.k[0]) / (e * c.k[1]);
279 // int i = (u < v ? 0 : 1);
281 return *this * c
.s
[i
];
284 inline QRgb
recolor(QRgb c
, int mode
, qreal g
)
286 if (mode
> 0 && mode
< 4) {
288 xyza r
= n
.gamma(g
) * rgb2lms
;
289 r
= r
.flatten(coef
[mode
-1]);
291 return r
.gamma(qreal(1.0) / g
).rgba();
294 return qRgb(qGray(c
), qGray(c
), qGray(c
));
300 QPixmap
ColorSim::recolor(const QPixmap
&pm
, int mode
, qreal gamma
)
302 // nothing to do if either paraneter is "bad"
303 if (pm
.isNull() || mode
> 4 || mode
< 1)
306 // get raw data in a format we can manipulate
307 QImage i
= pm
.toImage();
308 if (i
.format() != QImage::Format_RGB32
&& i
.format() != QImage::Format_ARGB32
)
309 i
= i
.convertToFormat(QImage::Format_ARGB32
);
311 int n
= i
.width() * i
.height();
312 QRgb
*d
= (QRgb
*)i
.bits();
313 for (int k
= 0; k
< n
; ++k
)
314 d
[k
] = ::recolor(d
[k
], mode
, gamma
);
316 return QPixmap::fromImage(i
);
318 // kate: indent-width 2;