1 /**
2   This module performs various color computation and conversions.
3   See_also: $(WEB http://www.brucelindbloom.com)
4   Bugs: Invalid assumptions are made about the XYZ space. It is actually not dependent on some RGB space.
5   Warning: <b>This module is alpha-quality and scheduled for deprecation. You probably didn't need it anyway.</b>.
6 */
7 module gfm.image.cie;
8 
9 import gfm.math.funcs,
10        gfm.math.vector,
11        gfm.math.matrix;
12 
13 // Standard illuminants (or reference whites) provide a basis for comparing colors recorded under different lighting.
14 enum ReferenceWhite
15 {
16     A,   /// A standard illuminant.
17     B,   /// B standard illuminant.
18     C,   /// C standard illuminant.
19     D50, /// 5003 K ("horizon" light), very common.
20     D55, /// D55 standard illuminant.
21     D65, /// 6504 K (noon light).
22     D75, /// D75 standard illuminant.
23     E,   /// equal-energy radiator.
24     F2,  /// F2 standard illuminant.
25     F7,  /// F7 standard illuminant.
26     F11  /// F11 standard illuminant.
27 }
28 
29 /// Define various RGB spaces, which all have a reference white,
30 /// a power curve and primary xyY coordinates.
31 enum RGBSpace
32 {
33     sRGB,             ///
34     ADOBE_RGB,        ///
35     APPLE_RGB,        ///
36     BEST_RGB,         ///
37     BETA_RGB,         ///
38     BRUCE_RGB,        ///
39     CIE_RGB,          ///
40     COLORMATCH_RGB,   ///
41     DON_RGB_4,        ///
42     ECI_RGB_V2,       ///
43     EKTA_SPACE_PS5,   ///
44     NTSC_RGB,         ///
45     PAL_SECAM_RGB,    ///
46     PROPHOTO_RGB,     ///
47     SMPTE_C_RGB,      ///
48     WIDE_GAMUT_RGB    ///
49 }
50 
51 
52 /// A spectral distribution of actual energy, from 360 to 780 nm, by 5 nm increments.
53 alias Vector!(float, 107u) SpectralDistribution;
54 
55 /// Spectral reflectance values. Can only be converted to a SpectralDistribution
56 /// when lit with a ReferenceWhite.
57 /// from 300 to 780 nm, by 5 nm increments
58 /// Reflectances are parameterized by a ReferenceWhite.
59 alias Vector!(float, 107u) SpectralReflectance;
60 
61 /// Converts spectral color into a XYZ space (parameterized by an illuminant)
62 deprecated("gfm.image.cie will be removed in near future") vec3f spectralToXYZColor(SpectralReflectance c, ReferenceWhite illuminant) pure nothrow
63 {
64     Vector!(float, 107u) c_lit = c * refWhiteToSpectralDistribution(illuminant);
65     return vec3f(dot(CIE_OBS_X2, c_lit),
66                  dot(CIE_OBS_Y2, c_lit),
67                  dot(CIE_OBS_Z2, c_lit));
68 }
69 
70 /// Converts from companded RGB to uncompanded RGB.
71 /// Input and output should be in the [0..1] range.
72 deprecated("gfm.image.cie will be removed in near future") vec3f toLinearRGB(RGBSpace space)(vec3f compandedRGB)
73 {
74     alias compandedRGB c;
75     final switch (getRGBSettings(space).companding)
76     {
77         case Companding.GAMMA:
78         {
79             float gamma = getRGBSettings(space).gamma;
80             return c ^^ gamma;
81         }
82 
83         case Companding.sRGB:
84         {
85             static s(float x)
86             {
87                 if (x <= 0.04045f)
88                     return x / 12.92f;
89                 else
90                     return ((x + 0.055f) / 1.055f) ^^ 2.4f;
91             }
92             return vec3f(s(c.x), s(c.y), s(c.z));
93         }
94 
95         case Companding.L_STAR:
96         {
97             static l(float x)
98             {
99                 const K = 903.3f;
100                 if (x <= 0.08f)
101                     return (x * 100) / K;
102                 else
103                     return ((x + 0.16f) / 1.16f) ^^ 3;
104             }
105             return vec3f(l(c.x), l(c.y), l(c.z));
106         }
107     }
108 }
109 
110 /// Converts from uncompanded RGB to companded RGB.
111 /// Input and output should be in the [0..1] range.
112 deprecated("gfm.image.cie will be removed in near future") vec3f toCompandedRGB(RGBSpace space)(vec3f compandedRGB)
113 {
114     alias compandedRGB c;
115     final switch (getRGBSettings(space).companding)
116     {
117         case Companding.GAMMA:
118         {
119             float invGamma = 1 / getRGBSettings(space).gamma;
120             return c ^^ invGamma;
121         }
122 
123         case Companding.sRGB:
124         {
125             static s(float x)
126             {
127                 if (x <= 0.0031308f)
128                     return x * 12.92f;
129                 else
130                     return 1.055f * (x ^^ (1 / 2.4f)) - 0.055f;
131             }
132             return vec3f(s(c.x), s(c.y), s(c.z));
133         }
134 
135         case Companding.L_STAR:
136         {
137             static l(float x)
138             {
139                 const K = 903.3f;
140                 if (x <= 0.008856f)
141                     return x * K / 100.0f;
142                 else
143                     return 1.16f * (x ^^ (1 / 3.0f)) - 0.16f;
144             }
145             return vec3f(l(c.x), l(c.y), l(c.z));
146         }
147     }
148 }
149 
150 /// Converts linear RGB to XYZ.
151 deprecated("gfm.image.cie will be removed in near future") vec3f linearRGBToXYZ(RGBSpace space)(vec3f rgb)
152 {
153     // TODO: make M compile-time
154     auto M = getRGBSettings(space).makeRGBToXYZMatrix();
155     return M * rgb;
156 }
157 
158 /// Converts XYZ to linear RGB.
159 deprecated("gfm.image.cie will be removed in near future") vec3f XYZToLinearRGB(RGBSpace space)(vec3f xyz)
160 {
161     // TODO: make M compile-time
162     auto M = getRGBSettings(space).makeXYZToRGBMatrix();
163     return M * xyz;
164 }
165 
166 /// Converts from such a XYZ space back to spectral reflectance.
167 /// Both spaces being parameterized by the same Illuminant.
168 deprecated("gfm.image.cie will be removed in near future") SpectralReflectance XYZToSpectralColor(vec3f XYZ) pure nothrow
169 {
170     return CIE_OBS_X2 * XYZ.x + CIE_OBS_Y2 * XYZ.y + CIE_OBS_Z2 * XYZ.z;
171 }
172 
173 private
174 {
175     // Defining spectrum table of CIE 1931 Standard Colorimetric Observer
176     // aka 2° observer.
177     enum SpectralDistribution CIE_OBS_X2 = SpectralDistribution
178     ([
179         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
180         0.0001299f, 0.0002321f, 0.0004149f, 0.0007416f, 0.001368f, // 360 nm
181         0.002236f, 0.004243f, 0.00765f, 0.01431f, 0.02319f,
182         0.04351f, 0.07763f, 0.13438f, 0.21477f, 0.2839f,
183         0.3285f, 0.34828f, 0.34806f, 0.3362f, 0.3187f,
184         0.2908f, 0.2511f, 0.19536f, 0.1421f, 0.09564f,
185         0.05795001f, 0.03201f, 0.0147f, 0.0049f, 0.0024f,
186         0.0093f, 0.0291f, 0.06327f, 0.1096f, 0.1655f,
187         0.2257499f, 0.2904f, 0.3597f, 0.4334499f, 0.5120501f,
188         0.5945f, 0.6784f, 0.7621f, 0.8425f, 0.9163f,
189         0.9786f, 1.0263f, 1.0567f, 1.0622f, 1.0456f,
190         1.0026f, 0.9384f, 0.8544499f, 0.7514f, 0.6424f,
191         0.5419f, 0.4479f, 0.3608f, 0.2835f, 0.2187f,
192         0.1649f, 0.1212f, 0.0874f, 0.0636f, 0.04677f,
193         0.0329f, 0.0227f, 0.01584f, 0.01135916f, 0.008110916f,
194         0.005790346f, 0.004106457f, 0.002899327f, 0.00204919f, 0.001439971f,
195         0.000999949f, 0.000690079f, 0.000476021f, 0.000332301f, 0.000234826f,
196         0.000166151f, 0.000117413f, 8.30753e-05f, 5.87065e-05f, 4.15099e-05f,
197         2.93533e-05f, 2.06738e-05f, 1.45598e-05f, 1.0254e-05f, 7.22146e-06f,
198         5.08587e-06f, 3.58165e-06f, 2.52253e-06f, 1.77651e-06f, 1.25114e-06f
199     ]);
200 
201     enum SpectralDistribution CIE_OBS_Y2 = SpectralDistribution
202     ([
203         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
204         0.000003917f, 0.000006965f, 0.00001239f, 0.00002202f, 0.000039f, // 360 nm
205         0.000064f, 0.00012f, 0.000217f, 0.000396f, 0.00064f,
206         0.00121f, 0.00218f, 0.004f, 0.0073f, 0.0116f,
207         0.01684f, 0.023f, 0.0298f, 0.038f, 0.048f,
208         0.06f, 0.0739f, 0.09098f, 0.1126f, 0.13902f,
209         0.1693f, 0.20802f, 0.2586f, 0.323f, 0.4073f,
210         0.503f, 0.6082f, 0.71f, 0.7932f, 0.862f,
211         0.9148501f, 0.954f, 0.9803f, 0.9949501f, 1.0f,
212         0.995f, 0.9786f, 0.952f, 0.9154f, 0.87f,
213         0.8163f, 0.757f, 0.6949f, 0.631f, 0.5668f,
214         0.503f, 0.4412f, 0.381f, 0.321f, 0.265f,
215         0.217f, 0.175f, 0.1382f, 0.107f, 0.0816f,
216         0.061f, 0.04458f, 0.032f, 0.0232f, 0.017f,
217         0.01192f, 0.00821f, 0.005723f, 0.004102f, 0.002929f,
218         0.002091f, 0.001484f, 0.001047f, 0.00074f, 0.00052f,
219         0.0003611f, 0.0002492f, 0.0001719f, 0.00012f, 0.0000848f,
220         0.00006f, 0.0000424f, 0.00003f, 0.0000212f, 0.00001499f,
221         0.0000106f, 7.4657e-06f, 5.2578e-06f, 3.7029e-06f, 2.6078e-06f,
222         1.8366e-06f, 1.2934e-06f, 9.1093e-07f, 6.4153e-07f, 4.5181e-07f
223     ]);
224 
225     enum SpectralDistribution CIE_OBS_Z2 = SpectralDistribution
226     ([
227         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
228         0.0006061f, 0.001086f, 0.001946f, 0.003486f, 0.006450001f, // 360 nm
229         0.01054999f, 0.02005001f, 0.03621f, 0.06785001f, 0.1102f,
230         0.2074f, 0.3713f, 0.6456f, 1.0390501f, 1.3856f,
231         1.62296f, 1.74706f, 1.7826f, 1.77211f, 1.7441f,
232         1.6692f, 1.5281f, 1.28764f, 1.0419f, 0.8129501f,
233         0.6162f, 0.46518f, 0.3533f, 0.272f, 0.2123f,
234         0.1582f, 0.1117f, 0.07824999f, 0.05725001f, 0.04216f,
235         0.02984f, 0.0203f, 0.0134f, 0.008749999f, 0.005749999f,
236         0.0039f, 0.002749999f, 0.0021f, 0.0018f, 0.001650001f,
237         0.0014f, 0.0011f, 0.001f, 0.0008f, 0.0006f,
238         0.00034f, 0.00024f, 0.00019f, 0.0001f, 5e-05f,
239         0.00003f, 0.00002f, 0.00001f, 0.0f, 0.0f,
240         0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
241         0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
242         0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
243         0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
244         0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
245         0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
246         0.0f, 0.0f, 0.0f, 0.0f, 0.0f
247     ]);
248 
249     enum SpectralDistribution CIE_D50 = makeDDistribution(5004);
250     enum SpectralDistribution CIE_D55 = makeDDistribution(5504);
251     enum SpectralDistribution CIE_D65 = makeDDistribution(6504);
252     enum SpectralDistribution CIE_D75 = makeDDistribution(7504);
253 
254     vec3f refWhiteToXYZ(ReferenceWhite white) pure nothrow
255     {
256         final switch(white)
257         {
258             case ReferenceWhite.A:   return vec3f( 1.0985f, 1.0f, 0.35585f);
259             case ReferenceWhite.B:   return vec3f(0.99072f, 1.0f, 0.85223f);
260             case ReferenceWhite.C:   return vec3f(0.98074f, 1.0f, 1.18232f);
261             case ReferenceWhite.D50: return vec3f(0.96422f, 1.0f, 0.82521f);
262             case ReferenceWhite.D55: return vec3f(0.95682f, 1.0f, 0.92149f);
263             case ReferenceWhite.D65: return vec3f(0.95047f, 1.0f, 1.08883f);
264             case ReferenceWhite.D75: return vec3f(0.94972f, 1.0f, 1.22638f);
265             case ReferenceWhite.E:   return vec3f(    1.0f, 1.0f,     1.0f);
266             case ReferenceWhite.F2:  return vec3f(0.99186f, 1.0f, 0.67393f);
267             case ReferenceWhite.F7:  return vec3f(0.95041f, 1.0f, 1.08747f);
268             case ReferenceWhite.F11: return vec3f(1.00962f, 1.0f, 0.64350f);
269         }
270     }
271 
272     SpectralDistribution refWhiteToSpectralDistribution(ReferenceWhite illuminant) pure nothrow
273     {
274         final switch(illuminant)
275         {
276             // TODO: think about normalization and what it means
277             case ReferenceWhite.D50: return CIE_D50;
278             case ReferenceWhite.D55: return CIE_D55;
279             case ReferenceWhite.D65: return CIE_D65;
280             case ReferenceWhite.D75: return CIE_D75;
281 
282             // TODO: actual reference white for C, E, F*?
283             case ReferenceWhite.A:
284             case ReferenceWhite.B:
285             case ReferenceWhite.C:
286             case ReferenceWhite.E:
287             case ReferenceWhite.F2:
288             case ReferenceWhite.F7:
289             case ReferenceWhite.F11:
290             {
291                 // TODO: precalc
292                 vec3f XYZ = refWhiteToXYZ(illuminant);
293                 return CIE_OBS_X2 * XYZ.x + CIE_OBS_Y2 * XYZ.y + CIE_OBS_Z2 * XYZ.z;
294             }
295         }
296     }
297 
298     // return a (X, Y, Z) triplet from a (x, y, Y) triplet
299     vec3f xyYToXYZColor(vec3f xyY) pure nothrow
300     {
301         assert(xyY.x >= 0 && xyY.x <= 1
302                && xyY.y >= 0 && xyY.y <= 1
303                && xyY.z >= 0 && xyY.z <= 1);
304         if (xyY.y == 0)
305         {
306             return vec3f(0.0f);
307         }
308         else
309         {
310             vec3f res = void;
311             res.x = xyY.x * xyY.z / xyY.y;
312             res.y = xyY.z;
313             res.z = (1 - xyY.x - xyY.y) * xyY.z / xyY.y;
314             return res;
315         }
316     }
317 }
318 
319 private
320 {
321     enum Companding
322     {
323         GAMMA,
324         sRGB,
325         L_STAR
326     }
327 
328     struct RGBSpaceConf
329     {
330         Companding companding;
331         float gamma;
332         ReferenceWhite refWhite;
333         float xRed, yRed, YRed;
334         float xGreen, yGreen, YGreen;
335         float xBlue, yBlue, YBlue;
336 
337         // return the 3x3 matrix to go from a RGB space to a XYZ space
338         // parameterized by the same reference white
339         mat3f makeXYZToRGBMatrix() pure const nothrow
340         {
341             // compute XYZ values of primaries
342             vec3f[3] rgb = void;
343             rgb[0] = xyYToXYZColor(vec3f(xRed, yRed, 1.0f));
344             rgb[1] = xyYToXYZColor(vec3f(xGreen, yGreen, 1.0f));
345             rgb[2] = xyYToXYZColor(vec3f(xBlue, yBlue, 1.0f));
346 
347             vec3f S = mat3f.fromRows(rgb[]).inverse() * refWhiteToXYZ(refWhite);
348             rgb[0] *= S;
349             rgb[1] *= S;
350             rgb[2] *= S;
351             return mat3f.fromRows(rgb[]);
352         }
353 
354         // return the 3x3 matrix to go from a, XYZ space to an RGB space
355         // the XYZ space must be parameterized with the RGB reference white
356         mat3f makeRGBToXYZMatrix() pure const nothrow
357         {
358             return makeXYZToRGBMatrix().inverse();
359         }
360     }
361 
362     // gives characteristics of known RGB space
363     RGBSpaceConf getRGBSettings(RGBSpace s)
364     {
365         final switch(s)
366         {
367             case RGBSpace.sRGB: return RGBSpaceConf(Companding.sRGB, float.nan, ReferenceWhite.D65, 0.6400f, 0.3300f, 0.212656f, 0.3000f, 0.6000f, 0.715158f, 0.1500f, 0.0600f, 0.072186f);
368             case RGBSpace.ADOBE_RGB: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.D65, 0.6400f, 0.3300f, 0.297361f, 0.2100f, 0.7100f, 0.627355f, 0.1500f, 0.0600f, 0.075285f);
369             case RGBSpace.APPLE_RGB: return RGBSpaceConf(Companding.GAMMA, 1.8f, ReferenceWhite.D65, 0.6250f, 0.3400f, 0.244634f, 0.2800f, 0.5950f, 0.672034f, 0.1550f, 0.0700f, 0.083332f);
370             case RGBSpace.BEST_RGB: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.D50, 0.7347f, 0.2653f, 0.228457f, 0.2150f, 0.7750f, 0.737352f, 0.1300f, 0.0350f, 0.034191f);
371             case RGBSpace.BETA_RGB: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.D50, 0.6888f, 0.3112f, 0.303273f, 0.1986f, 0.7551f, 0.663786f, 0.1265f, 0.0352f, 0.032941f);
372             case RGBSpace.BRUCE_RGB: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.D65, 0.6400f, 0.3300f, 0.240995f, 0.2800f, 0.6500f, 0.683554f, 0.1500f, 0.0600f, 0.075452f);
373             case RGBSpace.CIE_RGB: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.E, 0.7350f, 0.2650f, 0.176204f, 0.2740f, 0.7170f, 0.812985f, 0.1670f, 0.0090f, 0.010811f);
374             case RGBSpace.COLORMATCH_RGB: return RGBSpaceConf(Companding.GAMMA, 1.8f, ReferenceWhite.D50, 0.6300f, 0.3400f, 0.274884f, 0.2950f, 0.6050f, 0.658132f, 0.1500f, 0.0750f, 0.066985f);
375             case RGBSpace.DON_RGB_4: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.D50, 0.6960f, 0.3000f, 0.278350f, 0.2150f, 0.7650f, 0.687970f, 0.1300f, 0.0350f, 0.033680f);
376             case RGBSpace.ECI_RGB_V2: return RGBSpaceConf(Companding.L_STAR, float.nan, ReferenceWhite.D50, 0.6700f, 0.3300f, 0.320250f, 0.2100f, 0.7100f, 0.602071f, 0.1400f, 0.0800f, 0.077679f);
377             case RGBSpace.EKTA_SPACE_PS5: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.D50, 0.6950f, 0.3050f, 0.260629f, 0.2600f, 0.7000f, 0.734946f, 0.1100f, 0.0050f, 0.004425f);
378             case RGBSpace.NTSC_RGB: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.C, 0.6700f, 0.3300f, 0.298839f, 0.2100f, 0.7100f, 0.586811f, 0.1400f, 0.0800f, 0.114350f);
379             case RGBSpace.PAL_SECAM_RGB: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.D65, 0.6400f, 0.3300f, 0.222021f, 0.2900f, 0.6000f, 0.706645f, 0.1500f, 0.0600f, 0.071334f);
380             case RGBSpace.PROPHOTO_RGB: return RGBSpaceConf(Companding.GAMMA, 1.8f, ReferenceWhite.D50, 0.7347f, 0.2653f, 0.288040f, 0.1596f, 0.8404f, 0.711874f, 0.0366f, 0.0001f, 0.000086f);
381             case RGBSpace.SMPTE_C_RGB: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.D65, 0.6300f, 0.3400f, 0.212395f, 0.3100f, 0.5950f, 0.701049f, 0.1550f, 0.0700f, 0.086556f);
382             case RGBSpace.WIDE_GAMUT_RGB: return RGBSpaceConf(Companding.GAMMA, 2.2f, ReferenceWhite.D50, 0.7350f, 0.2650f, 0.258187f, 0.1150f, 0.8260f, 0.724938f, 0.1570f, 0.0180f, 0.016875f);
383         }
384     }
385 
386     enum double[6] M1Data = [ -1.039, -0.785, -0.535, -0.295, -0.068, 0.145 ];
387     enum double[6] M2Data = [ 0.363, -0.198, -0.521, -0.689, -0.757, -0.760 ];
388 
389 
390     // S0, S1 and S2 to compute D illuminants spectral distribution
391     // (300 to 830 nm by 10 nm increments, normalized to 100 for 560 nm)
392     enum double[54] S0 =
393     [
394         0.04f, 6.0f, 29.60f, 55.30f, 57.30f, 61.80f, 61.5, 68.8, 63.4, 65.8,
395         94.8, 104.8, 105.90, 96.50, 113.90, 125.60, 125.50, 121.30, 121.30, 113.50,
396         113.10, 110.80, 106.50, 108.80, 105.3, 104.4, 100.0, 96.0, 95.10, 89.10,
397         90.50, 90.30, 88.4, 84.0, 85.1, 81.9, 82.6, 84.9, 81.3, 71.9,
398         74.3, 76.4, 63.30, 71.70, 77.0, 65.20, 47.70, 68.60, 65.0, 66.0,
399         61.0, 53.30, 58.9, 61.9
400     ];
401 
402     enum double[54] S1 =
403     [
404         0.02, 4.5, 22.4, 42.0, 40.6, 41.6, 38.0, 42.4, 38.5, 35.0,
405         43.4, 46.3, 43.9, 37.1, 36.7, 35.9, 32.6, 27.9, 24.3, 20.1,
406         16.2, 13.2, 8.6, 6.1, 4.2, 1.9, 0, -1.6, -3.5, -3.5,
407         -5.8, -7.2, -8.6, -9.5, -10.9, -10.7, -12.0, -14.0, -13.6, -12.0,
408         -13.3, -12.9, -10.60, -11.6, -12.20,  -10.20, -7.80, -11.2, -10.4, -10.6,
409         -9.7, -8.3, -9.3, -9.8
410     ];
411 
412     enum double[54] S2 =
413     [
414         0.0, 2.0, 4.0, 8.5, 7.8, 6.7, 5.3, 6.1, 3.0, 1.2,
415         -1.10, -0.50, -0.70, -1.20, -2.60, -2.90, -2.80, -2.60, -2.60, -1.80,
416         -1.5, -1.3, -1.2, -1, -0.5, -0.3, 0, 0.2, 0.5, 2.1,
417         3.2, 4.1, 4.7, 5.1, 6.7, 7.3, 8.6, 9.8, 10.2, 8.3,
418         9.6, 8.5, 7.0, 7.6, 8.0, 6.7, 5.2, 7.4, 6.8, 7.0,
419         6.4, 5.5, 6.1, 6.5
420     ];
421 
422     // return a D reference
423     SpectralDistribution makeDDistribution(double temperatureInK)
424     {
425         double M1, M2;
426 
427         if (temperatureInK < 5000)
428         {
429             M1 = M1Data[0];
430             M2 = M2Data[0];
431         }
432         else if (temperatureInK >= 7500)
433         {
434             M1 = M1Data[5];
435             M2 = M2Data[5];
436         }
437         else
438         {
439             for (int i = 0; i < 5; ++i)
440             {
441                 double T = 5000 + i * 500;
442                 if (temperatureInK < T)
443                 {
444                     M1 = lerp(M1Data[i], M1Data[i + 1], (temperatureInK - T) / 500);
445                     M2 = lerp(M2Data[i], M2Data[i + 1], (temperatureInK - T) / 500);
446                     break;
447                 }
448             }
449         }
450 
451         SpectralDistribution r;
452         for (int i = 0; i < 107; ++i)
453         {
454             double s0, s1, s2;
455             if (i % 2 == 0)
456             {
457                 s0 = S0[i/2];
458                 s1 = S1[i/2];
459                 s2 = S2[i/2];
460             }
461             else
462             {
463                 s0 = 0.5 * (S0[i/2] + S0[i/2+1]);
464                 s1 = 0.5 * (S1[i/2] + S1[i/2+1]);
465                 s2 = 0.5 * (S2[i/2] + S2[i/2+1]);
466             }
467             r.v[i] = s0 + M1 * s1 + M2 * s2;
468         }
469         return r;
470     }
471 }
472