1/***************************************************************************
2 colorsim.cpp - description
3 -------------------
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 ***************************************************************************/
8
9/***************************************************************************
10 * *
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. *
15 * *
16 ***************************************************************************/
17
18/***************************************************************************
19
20Citations:
21
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
27 dichromats."
28 Color Research and Application 24, 243-252.
29
30 ***************************************************************************/
31
32// application specific includes
33#include "colorsim.h"
34
35// include files for Qt
36#include <QtGui/QColor>
37
38#include <math.h>
39
40typedef qreal matrix[3][3];
41
42#define SIMPLE_ALGORITHM
43
44#ifndef SIMPLE_ALGORITHM
45typedef qreal vector[3];
46
47struct fcoef {
48 vector k[2];
49// vector e;
50 matrix s[2];
51};
52#endif
53
54class xyza {
55 private:
56 qreal x, y, z, a;
57
58 public:
59 xyza() {}
60 xyza(const QColor &c);
61 QRgb rgba() const;
62 xyza gamma(qreal q) const;
63 xyza operator*(const matrix m) const;
64#ifndef SIMPLE_ALGORITHM
65 xyza(const vector v);
66 qreal operator*(const vector m) const;
67 xyza flatten(fcoef c) const;
68#endif
69};
70
71xyza::xyza(const QColor &c) :
72 x(c.redF()), y(c.greenF()), z(c.blueF()), a(c.alphaF())
73{
74}
75
76inline qreal clamp(qreal n)
77{
78 return qMin(qreal(1.0), qMax(qreal(0.0), n));
79}
80
81QRgb xyza::rgba() const
82{
83 return QColor::fromRgbF(clamp(x), clamp(y), clamp(z), a).rgba();
84}
85
86xyza xyza::operator*(const matrix m) const
87{
88 xyza r;
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]);
92 r.a = a;
93 return r;
94}
95
96xyza xyza::gamma(qreal q) const
97{
98 xyza r;
99 r.x = pow(x, q);
100 r.y = pow(y, q);
101 r.z = pow(z, q);
102 r.a = a;
103 return r;
104}
105
106#if !defined(SIMPLE_ALGORITHM) || !defined(STANFORD_ALGORITHM)
107
108/***************************************************************************
109
110 These RGB<->LMS transformation matrices are from [1].
111
112 ***************************************************************************/
113
114static const matrix rgb2lms = {
115 {0.1992, 0.4112, 0.0742},
116 {0.0353, 0.2226, 0.0574},
117 {0.0185, 0.1231, 1.3550}
118};
119
120static const matrix lms2rgb = {
121 { 7.4645, -13.8882, 0.1796},
122 {-1.1852, 6.8053, -0.2234},
123 { 0.0058, -0.4286, 0.7558}
124};
125
126#endif
127
128#ifdef SIMPLE_ALGORITHM
129
130# ifndef STANFORD_ALGORITHM // from "Computerized simulation of color appearance for dichromats"
131
132# ifdef CRA_ALGORITHM
133
134/***************************************************************************
135
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.
139
140 ***************************************************************************/
141
142static 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} }
146};
147
148# else
149
150/***************************************************************************
151
152 These matrices are derived from the "Color Blindness Simulation" sample
153 palettes from Visolve, Ryobi System Solutions, using the LMS transformations
154 from [1].
155 http://www.ryobi-sol.co.jp/visolve/en/simulation.html
156
157 ***************************************************************************/
158
159static 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} }
163};
164
165# endif
166
167# else // from the "Analysis of Color Blindness" project
168
169/***************************************************************************
170
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
174 (red and cyan).
175 http://www.stanford.edu/~ofidaner/psych221_proj/colorblindness_project.htm
176
177 ***************************************************************************/
178
179static 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} }
183};
184
185static const matrix rgb2lms = {
186 {17.8824, 43.5161, 4.11935},
187 { 3.45565, 27.1554, 3.86714},
188 { 0.0299566, 0.184309, 1.46709}
189};
190
191static const matrix lms2rgb = {
192 { 0.080944447905, -0.130504409160, 0.116721066440},
193 {-0.010248533515, 0.054019326636, -0.113614708214},
194 {-0.000365296938, -0.004121614686, 0.693511404861}
195};
196
197# endif
198
199inline QRgb recolor(QRgb c, int mode, qreal g)
200{
201 if (mode > 0 && mode < 4) {
202 xyza n = QColor(c);
203 if (g != 1.0) {
204 xyza r = n.gamma(g) * rgb2lms * coef[mode-1] * lms2rgb;
205 return r.gamma(qreal(1.0) / g).rgba();
206 }
207 else {
208 xyza r = n * rgb2lms * coef[mode-1] * lms2rgb;
209 return r.rgba();
210 }
211 }
212 else {
213 return qRgb(qGray(c), qGray(c), qGray(c));
214 }
215}
216
217#else // from "Computerized simulation of color appearance for dichromats"
218
219/***************************************************************************
220
221 This code is based on [1]. The RGB<->LMS transformation matrices are declared
222 above.
223
224 ***************************************************************************/
225
226static const fcoef coef[3] = {
227 {
228 { {0.0, 0.0, 1.0}, {0.0, 1.0, 0.0} }, // k
229// { }, // e
230 // a { }
231 { // s
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} }
234 }
235 },
236 {
237 { {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0} }, // k
238// { }, // e
239 // a { }
240 { // s
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} }
243 }
244 },
245 {
246 { {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0} }, // k
247// { }, // e
248 // a { }
249 { // s
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} }
252 }
253 }
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;
261 */
262};
263
264xyza::xyza(const vector v) :
265 x(v[0]), y(v[1]), z(v[2]), a(0.0)
266{
267}
268
269qreal xyza::operator*(const vector v) const
270{
271 return (x * v[0]) + (y * v[1]) + (z * v[2]);
272}
273
274xyza xyza::flatten(fcoef c) const
275{
276// xyza e(c.e);
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);
280 int i = 0;
281 return *this * c.s[i];
282}
283
284inline QRgb recolor(QRgb c, int mode, qreal g)
285{
286 if (mode > 0 && mode < 4) {
287 xyza n = QColor(c);
288 xyza r = n.gamma(g) * rgb2lms;
289 r = r.flatten(coef[mode-1]) * lms2rgb;
290 return r.gamma(qreal(1.0) / g).rgba();
291 }
292 else {
293 const int g = qGray(c);
294 return qRgb(g,g,g);
295 }
296}
297
298#endif
299
300QImage ColorSim::recolor(const QImage &pm, int mode, qreal gamma)
301{
302 // get raw data in a format we can manipulate
303 QImage i = pm;
304 if (i.format() != QImage::Format_RGB32 && i.format() != QImage::Format_ARGB32)
305 i = i.convertToFormat(QImage::Format_ARGB32);
306
307 int n = i.width() * i.height();
308 QRgb *d = (QRgb*)i.bits();
309 for (int k = 0; k < n; ++k)
310 d[k] = ::recolor(d[k], mode, gamma);
311
312 return i;
313}
314// kate: indent-width 2;
315