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 | |
20 | Citations: |
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 | |
40 | typedef qreal matrix[3][3]; |
41 | |
42 | #define SIMPLE_ALGORITHM |
43 | |
44 | #ifndef SIMPLE_ALGORITHM |
45 | typedef qreal vector[3]; |
46 | |
47 | struct fcoef { |
48 | vector k[2]; |
49 | // vector e; |
50 | matrix s[2]; |
51 | }; |
52 | #endif |
53 | |
54 | class 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 | |
71 | xyza::xyza(const QColor &c) : |
72 | x(c.redF()), y(c.greenF()), z(c.blueF()), a(c.alphaF()) |
73 | { |
74 | } |
75 | |
76 | inline qreal clamp(qreal n) |
77 | { |
78 | return qMin(qreal(1.0), qMax(qreal(0.0), n)); |
79 | } |
80 | |
81 | QRgb xyza::rgba() const |
82 | { |
83 | return QColor::fromRgbF(clamp(x), clamp(y), clamp(z), a).rgba(); |
84 | } |
85 | |
86 | xyza 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 | |
96 | xyza 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 | |
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} |
118 | }; |
119 | |
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} |
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 | |
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} } |
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 | |
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} } |
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 | |
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} } |
183 | }; |
184 | |
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} |
189 | }; |
190 | |
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} |
195 | }; |
196 | |
197 | # endif |
198 | |
199 | inline 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 | |
226 | static 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 | |
264 | xyza::xyza(const vector v) : |
265 | x(v[0]), y(v[1]), z(v[2]), a(0.0) |
266 | { |
267 | } |
268 | |
269 | qreal xyza::operator*(const vector v) const |
270 | { |
271 | return (x * v[0]) + (y * v[1]) + (z * v[2]); |
272 | } |
273 | |
274 | xyza 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 | |
284 | inline 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 | |
300 | QImage 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 | |