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