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