1 /* Copyright (c) 2008-2021 The University of the West Indies
2  *
3  * Contact: robert.lancashire@uwimona.edu.jm
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General private
7  *  License as published by the Free Software Foundation; either
8  *  version 2.1 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General private License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General private
16  *  License along with this library; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 // CHANGES to 'visible.java' - module to predict colour of visible spectrum
21 // created July 2008 based on concept published by Darren L. Williams
22 // in J. Chem. Educ., 84(11), 1873-1877, 2007
23 //
24 // Judd-Vos-modified 1931 CIE 2-deg color matching functions (1978)
25 // The CIE standard observer functions were curve fitted using FitYK
26 // and the equations used for these calculations. The values obtained
27 // do not seem to vary appreciably from those published in the JChemEd article
28 
29 package jspecview.common;
30 
31 import java.lang.Math;
32 
33 import javajs.util.CU;
34 
35 import jspecview.api.VisibleInterface;
36 
37 /**
38  * Visible class - for prediction of colour from visible spectrum
39  *
40  * see
41  *
42  * Robert J. Lancashire and Craig A.D. Walters
43  * Colour prediction with JSpecView
44  * http://www.chemmantis.com/Article.aspx?id=850
45  *
46  * revised 24th Oct 2021 to redo curve fitting for D65 data
47  *
48  *
49  * and
50  *
51  * Darren L. Williams, Thomas J. Flaherty, Casie L. Jupe,
52  * Stephanie A. Coleman, Kara A. Marquez, and Jamie J. Stanton
53  * Beyond [lambda]max: Transforming Visible Spectra into 24-Bit Color Values
54  * J. Chem. Educ., 2007, 84 (11), p 1873 DOI: 10.1021/ed084p1873
55  * http://pubs.acs.org/doi/abs/10.1021/ed084p1873
56  *
57  * and
58  *
59  * Michael Stokes (Hewlett-Packard), Matthew Anderson (Microsoft),
60  * Srinivasan Chandrasekar (Microsoft), Ricardo Motta (Hewlett-Packard)
61  * A Standard Default Color Space for the Internet: sRGB
62  * Version 1.10, November 5, 1996
63  * http://www.color.org/sRGB.xalter
64  *
65  * @author Bob Hanson
66  * @author Craig Walters
67  * @author Prof Robert J. Lancashire
68  */
69 
70 public class Visible implements VisibleInterface {
71 
Visible()72 	public Visible() {
73 		// for reflection
74 	}
75 
76 	/**
77 	 * Returns the integer color of a solution based on its visible spectrum.
78 	 *
79 	 * @param spec
80 	 * @param useFitted
81 	 *          if true,  use curve fitted equations for CIE curves and every point;
82 	 *          if false, use exact CIE 5-nm data and interpolated values
83 	 * @return 0xFFRRGGBB
84 	 *
85 	 */
86 	@Override
getColour(Spectrum spec, boolean useFitted)87 	public int getColour(Spectrum spec, boolean useFitted) {
88 		Coordinate xyCoords[] = spec.getXYCoords();
89 		boolean isAbsorbance = spec.isAbsorbance();
90 		double[] xyzd = new double[4];
91 
92 		// the spectrum has been checked to ensure that
93 		// (a) it has a nm axis
94 		// (b) it can be switched between absorbance and transmittance
95 		// (c) it is continuous
96 		// (d) it has more than 30 values
97 		// (e) it has a range at least 400 - 700 nm
98 
99 		// Step 1. Determine the CIE tristimulus values
100 
101 		//if (useFitted) {
102 			getXYZfitted(xyCoords, isAbsorbance, xyzd);
103 		//} else {
104 			//getXYZinterpolated(xyCoords, isAbsorbance, xyzd);
105 		//}
106 
107 		xyzd[0] /= xyzd[3];
108 		xyzd[1] /= xyzd[3];
109 		xyzd[2] /= xyzd[3];
110 		//System.out.println(xyzd[0] + " " + xyzd[1] + " " + xyzd[2]);
111 
112 		// Step 2. Transform XYZ to ICC standard RGB values.
113 
114 		double rgb[] = new double[] {
115 				xyzd[0] *  3.2410 + xyzd[1] * -1.5374 + xyzd[2] * -0.4986,
116 				xyzd[0] * -0.9692 + xyzd[1] *  1.8760 + xyzd[2] *  0.0416,
117 				xyzd[0] *  0.0556 + xyzd[1] * -0.2040 + xyzd[2] *  1.0570 };
118 		//System.out.println(rgb[0] + " " + rgb[1] + " " + rgb[2]);
119 
120 		// Step 3. For CRT monitors, add gamma correction to the sRGB values.
121 
122 		double gamma = 2.4;
123 		for (int i = 0; i < 3; i++)
124 			rgb[i] = (rgb[i] > 0.00304 ? 1.055 * Math.pow(rgb[i], 1 / gamma) - 0.055
125 							: 12.92 * rgb[i]);
126 		//System.out.println(rgb[0] + " " + rgb[1] + " " + rgb[2]);
127 
128 		// Step 4. Convert gamma-corrected sRGB' to 8-bit (0-255) values.
129 		// Step 5. Package as 0xFFRRGGBB
130 
131 		int c = CU.rgb(fix(rgb[0]), fix(rgb[1]), fix(rgb[2]));
132 		//System.out.println(CU.colorPtFromInt(c));
133 		return c;
134 
135 	}
136 
fix(double d)137 	private static int fix(double d) {
138 		return (d <= 0 ? 0 : d >= 1 ? 255 : (int) Math.round(255 * d));
139 	}
140 
getXYZfitted(Coordinate[] xyCoords, boolean isAbsorbance, double[] xyzd)141 	private static void getXYZfitted(Coordinate[] xyCoords, boolean isAbsorbance,
142 			double[] xyzd) {
143 		// Lancashire method -- using actual data and curve-fit CIE data
144         // 1931 data used to match the J Chem Educ article
145 		// Approximate x-bar, y-bar, z-bar, and CIE D65 curves.
146 
147 		double cie, xb, yb, zb;
148 
149 		for (int i = xyCoords.length; --i >= 0;) {
150 			double x = xyCoords[i].getXVal();
151 			if (x < 400 || x > 700)
152 				continue;
153 
154 //			cie = gauss(85.7145, 2.05719E-5, x - 607.263)
155 //					+ gauss(57.7256, 0.000126451, x - 457.096);
156       cie = gauss(15.2438, 4.99542E-03, x - 412.281)
157             + gauss(92.747, 1.12996E-05, x-540.046)
158             + gauss(13.8872, 5.16966E-04, x-525.74)
159             + gauss(16.7377, 5.55018E-03, x-448.038)
160             + gauss(23.9973, 1.28306E-03, x-469.107)
161             + gauss(5.68614, 1.03616E-02, x-672.024);
162 			xb = gauss(1.06561, 0.000500819, x - 598.623)
163 					+ gauss(0.283831, 0.00292745, x - 435.734)
164 					+ gauss(0.113771, 0.00192849, x - 549.271)
165 					+ gauss(0.239103, 0.00255944, x - 460.547);
166 			yb = gauss(0.239617, 0.00117296, x - 530.517)
167 					+ gauss(0.910377, 0.000300984, x - 565.635)
168 					+ gauss(0.0311013, 0.00152386, x - 463.833);
169 			zb = gauss(0.988366, 0.00220336, x - 456.345)
170 					+ gauss(0.381551, 0.000848554, x - 450.871)
171 					+ gauss(0.355693, 0.000628546, x - 470.668)
172 					+ gauss(0.81862, 0.00471059, x - 433.144);
173 
174 			double y = xyCoords[i].getYVal();
175 			if (isAbsorbance)
176 				y = Math.pow(10, -Math.max(y, 0));
177 //			y = 1; // test for 255 255 255 gives 255 255 254
178 			xyzd[0] += y * xb * cie;
179 			xyzd[1] += y * yb * cie;
180 			xyzd[2] += y * zb * cie;
181 			xyzd[3] += yb * cie;
182 		}
183 	}
184 
gauss(double a, double b, double x)185 	private static double gauss(double a, double b, double x) {
186 		return a * Math.exp(-b * x * x);
187 	}
188 
189 //	private static void getXYZinterpolated(Coordinate[] xyCoords, boolean isAbsorbance, double[] xyzd) {
190 //		// Williams method -- using 5-nm interpolations and actual CIE data
191 //		for (int i = cie1931_D65.length / 5; --i >= 0;) {
192 //			int pt = (i + 1) * 5;
193 //			double cie = cie1931_D65[--pt];
194 //			double zb = cie1931_D65[--pt];
195 //			double yb = cie1931_D65[--pt];
196 //			double xb = cie1931_D65[--pt];
197 //			double x = cie1931_D65[--pt];
198 //			double y = Coordinate.getYValueAt(xyCoords, x);
199 //			if (isAbsorbance)
200 //				y = Math.pow(10, -Math.max(y, 0));
201 //			//y = 1;
202 //			xyzd[0] += y * xb * cie;
203 //			xyzd[1] += y * yb * cie;
204 //			xyzd[2] += y * zb * cie;
205 //			xyzd[3] += yb * cie;
206 //		}
207 //	}
208 //
209 //	private static double[] cie1931_D65 = {
210 //		// http://files.cie.co.at/204.xls
211 //		//  x_      y_       z_      D65
212 //		400,0.01431,0.000396,0.06785,82.7549,
213 //		405,0.02319,0.00064,0.1102,87.1204,
214 //		410,0.04351,0.00121,0.2074,91.486,
215 //		415,0.07763,0.00218,0.3713,92.4589,
216 //		420,0.13438,0.004,0.6456,93.4318,
217 //		425,0.21477,0.0073,1.03905,90.057,
218 //		430,0.2839,0.0116,1.3856,86.6823,
219 //		435,0.3285,0.01684,1.62296,95.7736,
220 //		440,0.34828,0.023,1.74706,104.865,
221 //		445,0.34806,0.0298,1.7826,110.936,
222 //		450,0.3362,0.038,1.77211,117.008,
223 //		455,0.3187,0.048,1.7441,117.41,
224 //		460,0.2908,0.06,1.6692,117.812,
225 //		465,0.2511,0.0739,1.5281,116.336,
226 //		470,0.19536,0.09098,1.28764,114.861,
227 //		475,0.1421,0.1126,1.0419,115.392,
228 //		480,0.09564,0.13902,0.81295,115.923,
229 //		485,0.05795,0.1693,0.6162,112.367,
230 //		490,0.03201,0.20802,0.46518,108.811,
231 //		495,0.0147,0.2586,0.3533,109.082,
232 //		500,0.0049,0.323,0.272,109.354,
233 //		505,0.0024,0.4073,0.2123,108.578,
234 //		510,0.0093,0.503,0.1582,107.802,
235 //		515,0.0291,0.6082,0.1117,106.296,
236 //		520,0.06327,0.71,0.07825,104.79,
237 //		525,0.1096,0.7932,0.05725,106.239,
238 //		530,0.1655,0.862,0.04216,107.689,
239 //		535,0.22575,0.91485,0.02984,106.047,
240 //		540,0.2904,0.954,0.0203,104.405,
241 //		545,0.3597,0.9803,0.0134,104.225,
242 //		550,0.43345,0.99495,0.00875,104.046,
243 //		555,0.51205,1,0.00575,102.023,
244 //		560,0.5945,0.995,0.0039,100,
245 //		565,0.6784,0.9786,0.00275,98.1671,
246 //		570,0.7621,0.952,0.0021,96.3342,
247 //		575,0.8425,0.9154,0.0018,96.0611,
248 //		580,0.9163,0.87,0.00165,95.788,
249 //		585,0.9786,0.8163,0.0014,92.2368,
250 //		590,1.0263,0.757,0.0011,88.6856,
251 //		595,1.0567,0.6949,0.001,89.3459,
252 //		600,1.0622,0.631,0.0008,90.0062,
253 //		605,1.0456,0.5668,0.0006,89.8026,
254 //		610,1.0026,0.503,0.00034,89.5991,
255 //		615,0.9384,0.4412,0.00024,88.6489,
256 //		620,0.85445,0.381,0.00019,87.6987,
257 //		625,0.7514,0.321,0.0001,85.4936,
258 //		630,0.6424,0.265,0.00005,83.2886,
259 //		635,0.5419,0.217,0.00003,83.4939,
260 //		640,0.4479,0.175,0.00002,83.6992,
261 //		645,0.3608,0.1382,0.00001,81.863,
262 //		650,0.2835,0.107,0,80.0268,
263 //		655,0.2187,0.0816,0,80.1207,
264 //		660,0.1649,0.061,0,80.2146,
265 //		665,0.1212,0.04458,0,81.2462,
266 //		670,0.0874,0.032,0,82.2778,
267 //		675,0.0636,0.0232,0,80.281,
268 //		680,0.04677,0.017,0,78.2842,
269 //		685,0.0329,0.01192,0,74.0027,
270 //		690,0.0227,0.00821,0,69.7213,
271 //		695,0.01584,0.005723,0,70.6652,
272 //		700,0.011359,0.004102,0,71.6091,
273 //	};
274 }
275