Main Page Class Hierarchy Compound List File List Compound Members File Members

Spectrum.cc

Go to the documentation of this file.
00001 /*
00002 File: Spectrum.cc
00003 
00004 Function: Provides classes for manipulating spectra
00005 
00006 Author: Andrew Willmott
00007 
00008 Notes: 
00009 
00010 */
00011 
00012 #include <stdlib.h>
00013 #include "Spectrum.h"
00014 #include "ColourSystem.h"
00015 #include "VecUtil.h"
00016 
00017 const Int Spectrum::kComponents = 100; // number of basis functions
00018 const Float Spectrum::kStart = 350.0; // start of spectrum
00019 const Float Spectrum::kEnd = 800.0; // end of spectrum
00020 
00021 // CIE colour matching functions, x_bar, y_bar and z_bar
00022 // 360-830nm in steps of 5nm
00023 // good source for this stuff is http://cvision.ucsd.edu/basicindex.htm
00024 
00025  const Float kCIE_Start = 360.0;
00026  const Float kCIE_End = 830.0;
00027  const Float kCIE_Delta = 5.0;
00028 
00029  static Float tCIE_xbar[95] =
00030 {
00031 #include "cie_xbar.spectrum" 
00032 };
00033 RegularSpectralCurve kCIE_xbar(tCIE_xbar, 360, 830, 95);
00034 
00035  static Float tCIE_ybar[95] =
00036 {
00037 #include "cie_ybar.spectrum" 
00038 };
00039 RegularSpectralCurve kCIE_ybar(tCIE_ybar, 360, 830, 95);
00040 
00041  static Float tCIE_zbar[95] =
00042 {
00043 #include "cie_zbar.spectrum" 
00044 };
00045 RegularSpectralCurve kCIE_zbar(tCIE_zbar, 360, 830, 95);
00046 
00047  Colour Spectrum::ToCIE_XYZ()
00048 {
00049 Int i;
00050 Colour c = vl_0;
00051 
00052 for (i = 0; i < kComponents; i++)
00053 {
00054 ClrReal x = (i + 0.5) / kComponents;
00055 ClrReal lambda = mix(kStart, kEnd, x);
00056 
00057 c[0] += kCIE_xbar.Sample(lambda) * SELF[i];
00058 c[1] += kCIE_ybar.Sample(lambda) * SELF[i];
00059 c[2] += kCIE_zbar.Sample(lambda) * SELF[i];
00060 }
00061 
00062 return(c);
00063 }
00064 
00065 // --- Spectral curve methods -------------------------------------------------
00066 
00067  SpectralCurve::~SpectralCurve()
00068 {
00069 }
00070 
00071  SpectralCurve::operator Spectrum()
00072 {
00073 Spectrum result;
00074 Float lambda, x;
00075 Int i;
00076 
00077 #ifdef USE_INT
00078 Float delta, integral, lastIntegral;
00079 cout << "INTEGRATING" << endl;
00080 delta = (Spectrum::kEnd - Spectrum::kStart) / Spectrum::kComponents;
00081 lambda = Spectrum::kStart;
00082 lastIntegral = Integral(lambda);
00083 
00084 for (i = 0; i < Spectrum::kComponents; i++)
00085 {
00086 lambda += delta;
00087 integral = Integral(lambda);
00088 // normalised box functions, so we divide by the support size
00089 result[i] = (integral - lastIntegral) / delta;
00090 lastIntegral = integral;
00091 }
00092 #else
00093 
00094 for (i = 0; i < Spectrum::kComponents; i++)
00095 {
00096 x = (i + 0.5) / Spectrum::kComponents;
00097 lambda = Spectrum::kStart + x * (Spectrum::kEnd - Spectrum::kStart);
00098 
00099 result[i] = Sample(lambda);
00100 }
00101 
00102 #endif
00103 
00104 return(result);
00105 }
00106 
00107  Float SpectralCurve::Integral(Float lambda) const
00108 {
00109 const Float kIntegrateDelta = 1.0;
00110 Float s, sum = 0.0;
00111 Int n = 0;
00112 
00113 for (s = Spectrum::kStart; s < lambda; s += kIntegrateDelta)
00114 {
00115 sum += Sample(s);
00116 n++;
00117 }
00118 
00119 if (n > 0)
00120 sum *= (lambda - Spectrum::kStart) / n;
00121 
00122 return(sum);
00123 }
00124 
00125  Float SpectralCurve::MaxCmpt() const
00126 {
00127 const Float kMaxDelta = 1.0;
00128 Float s, max = Sample(Spectrum::kStart);
00129 
00130 for (s = Spectrum::kStart + kMaxDelta; s <= Spectrum::kEnd; s += kMaxDelta)
00131 {
00132 max = Max(max, Sample(s));
00133 }
00134 
00135 return(max);
00136 }
00137 
00138  Void SpectralCurve::MakeImage(Image &image, ClrReal maxAmplitude)
00139 // Plot spectrum into image
00140 {
00141 Int i, j;
00142 Float *samples = new Float[image.Width()], ampMax = 0.0;
00143 Colour *waveColour = new Colour[image.Width()];
00144 
00145 image.Clear(cBlack);
00146 
00147 for (i = 0; i < image.Width(); i++)
00148 {
00149 Float x = (i + 0.5) / image.Width();
00150 Float lambda = mix(Spectrum::kStart, Spectrum::kEnd, x);
00151 
00152 samples[i] = Sample(lambda);
00153 waveColour[i] = WavelengthToXYZ(lambda);
00154 waveColour[i] = csDisplay.XYZToGamutClip(waveColour[i]);
00155 ampMax = Max(samples[i], ampMax);
00156 }
00157 
00158 if (maxAmplitude < 0.0)
00159 maxAmplitude = ampMax;
00160 
00161 for (j = 0; j < image.Height(); j++)
00162 { 
00163 Float y = maxAmplitude * (j + 0.5) / image.Height();
00164 
00165 for (i = 0; i < image.Width(); i++)
00166 if (samples[i] >= y)
00167 image.SetPixel(i, j, waveColour[i]);
00168 }
00169 
00170 delete[] samples;
00171 delete[] waveColour;
00172 }
00173 
00174  Colour SpectralCurve::ToCIE_XYZ()
00175 {
00176 Float lambda, sample;
00177 Colour c = vl_0;
00178 
00179 for (lambda = kCIE_Start; lambda <= kCIE_End; lambda += kCIE_Delta)
00180 {
00181 sample = Sample(lambda);
00182 c[0] += kCIE_xbar.Sample(lambda) * sample;
00183 c[1] += kCIE_ybar.Sample(lambda) * sample;
00184 c[2] += kCIE_zbar.Sample(lambda) * sample;
00185 }
00186 
00187 return(c);
00188 }
00189 
00190  static Float *MakeAccumData(Float *data, Int divisions)
00191 {
00192 Int i;
00193 Float *result, sum = 0;
00194 
00195 result = new Float[divisions];
00196 
00197 for (i = 0; i < divisions; i++)
00198 {
00199 sum += data[i];
00200 result[i] = sum;
00201 }
00202 
00203 return(result);
00204 }
00205 
00206  RegularSpectralCurve::RegularSpectralCurve(Float *sdata, Int start, Int end, Int divisions) : 
00207 data(sdata),
00208 waveDivs(divisions),
00209 waveStart(start),
00210 waveEnd(end)
00211 {
00212 waveDelta = (end - start) / (divisions - 1);
00213 accumData = MakeAccumData(data, divisions);
00214 }
00215 
00216  RegularSpectralCurve::~RegularSpectralCurve()
00217 {
00218 delete[] accumData;
00219 }
00220 
00221  Float RegularSpectralCurve::Sample(Float lambda) const
00222 // interpolate between two closest samples
00223 {
00224 Float result, x, dx;
00225 Int bin1, bin2;
00226 
00227 if (lambda < waveStart || lambda > waveEnd)
00228 return(0.0);
00229 
00230 x = (lambda - waveStart) / waveDelta;
00231 
00232 bin1 = (Int) x;
00233 dx = x - bin1;
00234 if (dx > 1e-8)
00235 bin2 = bin1 + 1;
00236 else
00237 bin2 = bin1;
00238 
00239 CheckRange(bin1, 0, waveDivs, "Bad bin number");
00240 CheckRange(bin2, 0, waveDivs, "Bad bin number");
00241 
00242 result = mix(data[bin1], data[bin2], dx);
00243 
00244 return(result);
00245 }
00246 
00247 #ifdef UNFINISHED
00248 Float RegularSpectralCurve::Integral(Float lambda) const
00249 // interpolate between two closest samples
00250 {
00251 Float result, x, dx;
00252 Int bin1, bin2;
00253 
00254 if (lambda < waveStart)
00255 return(0.0);
00256 else if (lambda > waveEnd)
00257 return(waveDelta * accumData[waveDivs - 1]);
00258 
00259 x = (lambda - waveStart) / waveDelta;
00260 
00261 bin1 = (Int) x;
00262 dx = x - bin1;
00263 if (dx > 1e-8)
00264 bin2 = bin1 + 1;
00265 else
00266 bin2 = bin1;
00267 
00268 CheckRange(bin1, 0, waveDivs, "Bad bin number");
00269 CheckRange(bin2, 0, waveDivs, "Bad bin number");
00270 
00271 result = waveDelta * mix(accumData[bin1], accumData[bin2], dx);
00272 
00273 return(result);
00274 }
00275 #endif
00276 
00277  IrregularSpectralCurve::IrregularSpectralCurve(Float *amps, Float *wavs, Int divs) :
00278 amplitudes(amps),
00279 wavelengths(wavs),
00280 divisions(divs)
00281 {
00282 accumAmplitudes = MakeAccumData(amps, divs);
00283 }
00284 
00285  IrregularSpectralCurve::IrregularSpectralCurve() :
00286 amplitudes(0),
00287 wavelengths(0),
00288 divisions(0),
00289 accumAmplitudes(0)
00290 {
00291 }
00292 
00293  IrregularSpectralCurve::~IrregularSpectralCurve()
00294 {
00295 delete[] accumAmplitudes;
00296 }
00297 
00298  IrregularSpectralCurve &IrregularSpectralCurve::ReadSDFile(FileName filename)
00299 {
00300 String line;
00301 StrConstArray tokens;
00302 ifstream s;
00303 Int i = 0;
00304 
00305 cout << "parsing " << filename.GetPath() << endl;
00306 
00307 s.open(filename.GetPath());
00308 
00309 if (!s)
00310 {
00311 cerr << "(ReadSDFile) Cannot access " << filename.GetPath() << endl;
00312 return(SELF);
00313 }
00314 
00315 while (s)
00316 {
00317 if (line.ReadLine(s))
00318 {
00319 Split(line, tokens);
00320 if (tokens.NumItems() == 0 || tokens[0][0] == '#')
00321 ;
00322 else if (tokens.NumItems() == 1)
00323 {
00324 divisions = atoi(tokens[0]);
00325 cout << "setting size to " << divisions << endl;
00326 delete[] wavelengths;
00327 delete[] amplitudes;
00328 wavelengths = new Float[divisions];
00329 amplitudes = new Float[divisions];
00330 i = 0;
00331 }
00332 else if (tokens.NumItems() >= 2)
00333 {
00334 if (!wavelengths)
00335 _Error("(ReadSDFile) didn't find number of divisions");
00336 wavelengths[i] = atof(tokens[0]);
00337 amplitudes[i] = atof(tokens[1]);
00338 i++;
00339 }
00340 }
00341 } 
00342 
00343 s.close();
00344 accumAmplitudes = MakeAccumData(amplitudes, divisions);
00345 
00346 return(SELF);
00347 }
00348 
00349  Float IrregularSpectralCurve::Sample(Float lambda) const
00350 // interpolate between two closest samples
00351 // need binary search or something to speed this up. (cache?)
00352 // currently we extend the endpoint samples out to infinity either
00353 // side. Perhaps should ramp down over X nm instead?
00354 {
00355 Float x, result;
00356 Int i;
00357 
00358 if (divisions == 0)
00359 return(0.0);
00360 else if (divisions == 1)
00361 return(amplitudes[0]);
00362 
00363 if (lambda < wavelengths[0])
00364 return(amplitudes[0]);
00365 
00366 i = 1;
00367 while (i < divisions)
00368 if (lambda < wavelengths[i])
00369 {
00370 x = (lambda - wavelengths[i - 1])
00371 / (wavelengths[i] - wavelengths[i - 1]);
00372 
00373 result = mix(amplitudes[i - 1], amplitudes[i], x);
00374 
00375 return(result);
00376 }
00377 else
00378 i++;
00379 
00380 return(amplitudes[divisions - 1]);
00381 }
00382 
00383 #ifdef UNFINISHED
00384 Float IrregularSpectralCurve::Integral(Float lambda) const
00385 // interpolate between two closest samples
00386 // need binary search or something to speed this up. (cache?)
00387 // currently we extend the endpoint samples out to infinity either
00388 // side. Perhaps should ramp down over X nm instead?
00389 {
00390 Float x, result;
00391 Int i;
00392 
00393 if (divisions == 0)
00394 return(0.0);
00395 else if (divisions == 1)
00396 return(amplitudes[0]);
00397 
00398 if (lambda < wavelengths[0])
00399 return(amplitudes[0]);
00400 
00401 i = 1;
00402 while (i < divisions)
00403 if (lambda < wavelengths[i])
00404 {
00405 x = (lambda - wavelengths[i - 1])
00406 / (wavelengths[i] - wavelengths[i - 1]);
00407 
00408 result = mix(amplitudes[i - 1], amplitudes[i], x);
00409 
00410 return(result);
00411 }
00412 else
00413 i++;
00414 
00415 return(amplitudes[divisions - 1]);
00416 }
00417 #endif
00418 
00419 // --- Utility routines -------------------------------------------------------
00420 
00421  Colour WavelengthToXYZ(ClrReal lambda)
00422 {
00423 Colour c;
00424 
00425 // calculate XYZ
00426 c[0] = kCIE_xbar.Sample(lambda);
00427 c[1] = kCIE_ybar.Sample(lambda);
00428 c[2] = kCIE_zbar.Sample(lambda);
00429 
00430 return(c);
00431 }
00432 
00433 // chromaticity stuff originally from utah's sunsky code
00434 
00435 // 300-830 10nm
00436  static Float tS0Amplitudes[54] =
00437 {
00438 0.04,6.0,29.6,55.3,57.3,
00439 61.8,61.5,68.8,63.4,65.8,
00440 94.8,104.8,105.9,96.8,113.9,
00441 125.6,125.5,121.3,121.3,113.5,
00442 113.1,110.8,106.5,108.8,105.3,
00443 104.4,100.0,96.0,95.1,89.1,
00444 90.5,90.3,88.4,84.0,85.1,
00445 81.9,82.6,84.9,81.3,71.9,
00446 74.3,76.4,63.3,71.7,77.0,
00447 65.2,47.7,68.6,65.0,66.0,
00448 61.0,53.3,58.9,61.9
00449 };
00450 
00451  static Float tS1Amplitudes[54] =
00452 {
00453 0.02,4.5,22.4,42.0,40.6,
00454 41.6,38.0,42.4,38.5,35.0,
00455 43.4,46.3,43.9,37.1,36.7,
00456 35.9,32.6,27.9,24.3,20.1,
00457 16.2,13.2,8.6,6.1,4.2,
00458 1.9,0.0,-1.6,-3.5,-3.5,
00459 -5.8,-7.2,-8.6,-9.5,-10.9,
00460 -10.7,-12.0,-14.0,-13.6,-12.0,
00461 -13.3,-12.9,-10.6,-11.6,-12.2,
00462 -10.2,-7.8,-11.2,-10.4,-10.6,
00463 -9.7,-8.3,-9.3,-9.8
00464 };
00465 
00466  static Float tS2Amplitudes[54] =
00467 {
00468 0.0,2.0,4.0,8.5,7.8,
00469 6.7,5.3,6.1,3.0,1.2,
00470 -1.1,-0.5,-0.7,-1.2,-2.6,
00471 -2.9,-2.8,-2.6,-2.6,-1.8,
00472 -1.5,-1.3,-1.2,-1.0,-0.5,
00473 -0.3,0.0,0.2,0.5,2.1,
00474 3.2,4.1,4.7,5.1,6.7,
00475 7.3,8.6,9.8,10.2,8.3,
00476 9.6,8.5,7.0,7.6,8.0,
00477 6.7,5.2,7.4,6.8,7.0,
00478 6.4,5.5,6.1,6.5
00479 };
00480 
00481 RegularSpectralCurve kS0Spectrum(tS0Amplitudes, 300, 830, 54);
00482 RegularSpectralCurve kS1Spectrum(tS1Amplitudes, 300, 830, 54);
00483 RegularSpectralCurve kS2Spectrum(tS2Amplitudes, 300, 830, 54);
00484 
00485  ChromaticitySpectrum::ChromaticitySpectrum(ClrReal x, ClrReal y)
00486 {
00487 M1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / (0.0241 + 0.2562 * x - 0.7341 * y);
00488 M2 = ( 0.03 -31.4424 * x + 30.0717 * y) / (0.0241 + 0.2562 * x - 0.7341 * y);
00489 }
00490 
00491  Float ChromaticitySpectrum::Sample(Float lambda) const
00492 {
00493 return(kS0Spectrum.Sample(lambda) + M1 * kS1Spectrum.Sample(lambda)
00494 + M2 * kS2Spectrum.Sample(lambda));
00495 }
00496 
00497  static Coord WavelengthToChroma(Float lambda)
00498 {
00499 Colour c;
00500 
00501 // calculate XYZ
00502 c[0] = kCIE_xbar.Sample(lambda);
00503 c[1] = kCIE_ybar.Sample(lambda);
00504 c[2] = kCIE_zbar.Sample(lambda);
00505 
00506 c /= (c[0] + c[1] + c[2]);
00507 
00508 return(Coord(c[0], c[1]));
00509 }
00510 
00511  Void DrawChromaticityImage(Image &image)
00512 // Plot CIE colour diagram
00513 {
00514 Int i, j;
00515 Colour chromaClr;
00516 Coord a1, a2, b1, b2;
00517 Float aLambda, bLambda;
00518 Int startX, endX;
00519 Bool lastClip, clip;
00520 
00521 // the only tricky part to drawing the CIE
00522 // diagram is culling everything outside the (x,y)
00523 // curve defined by the pure wavelengths, and the
00524 // "line of purples" between the highest and lowest
00525 // wavelength in the spectrum.
00526 
00527 // to take care of this we use a1/a2/b1/b2 to step
00528 // through the spectral curve from either end, starting
00529 // from the beginning, and pick the correct start and
00530 // ending x values for each scanline.
00531 
00532 image.Clear(cGrey50);
00533 
00534 a1 = WavelengthToChroma(kCIE_Start);
00535 aLambda = kCIE_Start + kCIE_Delta;
00536 a2 = WavelengthToChroma(aLambda);
00537 b1 = a1;
00538 bLambda = kCIE_End;
00539 b2 = WavelengthToChroma(bLambda);
00540 
00541 for (j = 0; j < image.Height(); j++)
00542 { 
00543 Float y = (j + 0.5) / image.Height();
00544 
00545 if (y < a1[1])
00546 continue;
00547 // use 'while' here because some consecutive wavelength
00548 // samples will have the same (x,y) values
00549 while (y > a2[1])
00550 {
00551 a1 = a2;
00552 aLambda += kCIE_Delta;
00553 a2 = WavelengthToChroma(aLambda);
00554 }
00555 while (y > b2[1])
00556 {
00557 b1 = b2;
00558 bLambda -= kCIE_Delta;
00559 b2 = WavelengthToChroma(bLambda);
00560 }
00561 
00562 if (bLambda < aLambda)
00563 // all done
00564 break;
00565 
00566 startX = Int(image.Width() * mix(a1[0], a2[0], (y - a1[1]) / (a2[1] - a1[1])));
00567 endX = Int(image.Width() * mix(b1[0], b2[0], (y - b1[1]) / (b2[1] - b1[1])));
00568 if (endX >= image.Width())
00569 endX = image.Width() - 1;
00570 
00571 lastClip = true;
00572 
00573 for (i = startX; i < endX; i++)
00574 {
00575 Float x = (i + 0.5) / image.Width();
00576 
00577 chromaClr = Colour(x, y, (1.0 - x - y));
00578 // chromaClr = Colour(x / y, 1.0, (1.0 - x - y) / y); 
00579 clip = MinCmpt(csDisplay.fromXYZ * chromaClr) < 0.0;
00580 if (clip != lastClip)
00581 {
00582 image.SetPixel(i, j, cYellow);
00583 lastClip = clip;
00584 }
00585 else
00586 {
00587 chromaClr = csDisplay.XYZToGamutClip(chromaClr);
00588 image.SetPixel(i, j, chromaClr);
00589 }
00590 }
00591 }
00592 }
00593 
00594  Colour CIEXYZToLuv(const Colour &cXYZ, const Colour &cWhiteXYZ)
00595 {
00596 ClrReal lumRatio = cXYZ[1] / cWhiteXYZ[1];
00597 const Colour denomRat(1.0, 15.0, 3.0);
00598 ClrReal denom, u, v, lumStar;
00599 Colour result;
00600 
00601 if (lumRatio <= 0.008856)
00602 lumStar = 903.3 * lumRatio;
00603 else
00604 lumStar = 116.0 * pow(lumRatio, 1.0 / 3.0) - 16.0;
00605 
00606 denom = dot(denomRat, cXYZ);
00607 u = 4.0 * cXYZ[0] / denom;
00608 v = 9.0 * cXYZ[1] / denom;
00609 denom = dot(denomRat, cWhiteXYZ);
00610 u -= 4.0 * cWhiteXYZ[0] / denom;
00611 v -= 9.0 * cWhiteXYZ[1] / denom;
00612 
00613 result[0] = lumStar;
00614 result[1] = 13.0 * lumStar * u;
00615 result[2] = 13.0 * lumStar * v;
00616 
00617 return(result);
00618 }
00619 
00620  BlackBodySpectrum::BlackBodySpectrum(Float temperature)
00621 {
00622 temp = temperature;
00623 }
00624 
00625  Float BlackBodySpectrum::Sample(Float lambda) const
00626 {
00627 Float wavelength = lambda * 1e-9;
00628 Float result;
00629 
00630 result = 3.74183e-16 * pow(wavelength, -5.0);
00631 result /= exp(1.4388e-2 / (wavelength * temp)) - 1.0;
00632 
00633 return(result);
00634 }

Generated at Sat Aug 5 00:17:04 2000 for Graphics Class Library by doxygen 1.1.0 written by Dimitri van Heesch, © 1997-2000

AltStyle によって変換されたページ (->オリジナル) /