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</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 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 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 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 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 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 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 473 unittest 474 { 475 vec3f white = vec3f(1.0f); 476 477 vec3f XYZ = linearRGBToXYZ!(RGBSpace.sRGB)(toLinearRGB!(RGBSpace.sRGB)(white)); 478 vec3f rgb = toCompandedRGB!(RGBSpace.sRGB)(XYZToLinearRGB!(RGBSpace.sRGB)(XYZ)); 479 480 assert(white.distanceTo(rgb) < 1e-3f); 481 }