RGB values of visible spectrum

I need an algorithm or function to map each wavelength of visible range of spectrum to its equivalent RGB values. Is there any structural relation between the RGB System and wavelength of a light? like this image: alt text
(source: kms at www1.appstate.edu)

sorry if this was irrelevant :-]


Solution 1:

I recently found out that my spectral colors don't work properly because they were based on nonlinear and shifted data. So I did little research and data compilation and found out that most spectrum images out there are incorrect. Also, the color ranges do not match to each other, so I used from this point only linearized real spectroscopy data like this

Here is the rectified output of mine:

spectral colors

  • the first spectrum is the best rendered spectrum I found but still way off the real thing
  • the second one is linearized Spectrum of our Sun taken from Earth
  • the last one is my current color output

Below are the RGB graphs:

This is the merge of both graphs:

graph merge

Now the code:

void spectral_color(double &r,double &g,double &b,double l) // RGB <0,1> <- lambda l <400,700> [nm]
    {
    double t;  r=0.0; g=0.0; b=0.0;
         if ((l>=400.0)&&(l<410.0)) { t=(l-400.0)/(410.0-400.0); r=    +(0.33*t)-(0.20*t*t); }
    else if ((l>=410.0)&&(l<475.0)) { t=(l-410.0)/(475.0-410.0); r=0.14         -(0.13*t*t); }
    else if ((l>=545.0)&&(l<595.0)) { t=(l-545.0)/(595.0-545.0); r=    +(1.98*t)-(     t*t); }
    else if ((l>=595.0)&&(l<650.0)) { t=(l-595.0)/(650.0-595.0); r=0.98+(0.06*t)-(0.40*t*t); }
    else if ((l>=650.0)&&(l<700.0)) { t=(l-650.0)/(700.0-650.0); r=0.65-(0.84*t)+(0.20*t*t); }
         if ((l>=415.0)&&(l<475.0)) { t=(l-415.0)/(475.0-415.0); g=             +(0.80*t*t); }
    else if ((l>=475.0)&&(l<590.0)) { t=(l-475.0)/(590.0-475.0); g=0.8 +(0.76*t)-(0.80*t*t); }
    else if ((l>=585.0)&&(l<639.0)) { t=(l-585.0)/(639.0-585.0); g=0.84-(0.84*t)           ; }
         if ((l>=400.0)&&(l<475.0)) { t=(l-400.0)/(475.0-400.0); b=    +(2.20*t)-(1.50*t*t); }
    else if ((l>=475.0)&&(l<560.0)) { t=(l-475.0)/(560.0-475.0); b=0.7 -(     t)+(0.30*t*t); }
    }
//--------------------------------------------------------------------------

Where

  • l is the wavelength in [nm] usable valueas are l = < 400.0 , 700.0 >
  • r,g,b are returning color components in range < 0.0 , 1.0 >

Solution 2:

Partial "Approximate RGB values for Visible Wavelengths"

Credit: Dan Bruton - Color Science

Original FORTRAN code @ (http://www.physics.sfasu.edu/astro/color/spectra.html)

Will return smooth(continuous) spectrum, heavy on the red side.

w - wavelength, R, G and B - color components

Ignoring gamma and intensity simple leaves:

if w >= 380 and w < 440:
    R = -(w - 440.) / (440. - 380.)
    G = 0.0
    B = 1.0
elif w >= 440 and w < 490:
    R = 0.0
    G = (w - 440.) / (490. - 440.)
    B = 1.0
elif w >= 490 and w < 510:
    R = 0.0
    G = 1.0
    B = -(w - 510.) / (510. - 490.)
elif w >= 510 and w < 580:
    R = (w - 510.) / (580. - 510.)
    G = 1.0
    B = 0.0
elif w >= 580 and w < 645:
    R = 1.0
    G = -(w - 645.) / (645. - 580.)
    B = 0.0
elif w >= 645 and w <= 780:
    R = 1.0
    G = 0.0
    B = 0.0
else:
    R = 0.0
    G = 0.0
    B = 0.0

Solution 3:

To convert a wavelength into an RGB color

First you consult a CIE 1964 Supplementary Standard Colorimetric Observer chart (archive)

https://imgur.com/a/JDatZNm

and look up the CIE color matching function values for the wavelength you want.

For example, i want to get the color of 455 nm light:

enter image description here

For our desired wavelength:

| nm  | CIE color matching functions  |  Chromacity coordinates     |
| nm  |     X    |     Y    |    Z    |    x    |    y    |    z    |
|-----|----------|----------|---------|---------|---------|---------| 
| 455 | 0.342957 | 0.106256 | 1.90070 | 0.14594 | 0.04522 | 0.80884 |

Note: The chromacity coordinates are simply calculated from the CIE color matching functions:

x = X / (X+Y+Z)
y = Y / (X+Y+Z)
z = Z / (Z+Y+Z)

Given that:

X+Y+Z = 0.342257+0.106256+1.90070 = 2.349913

we calculate:

x = 0.342257 / 2.349913 = 0.145945
y = 0.106256 / 2.349913 = 0.045217
z = 1.900700 / 2.349913 = 0.808838

You have your 455 nm light specified using two different color spaces:

  • XYZ: (0.342957, 0.106256, 1.900700)
  • xyz: (0.145945, 0.045217, 0.808838)

We can also add a third color space: xyY

x = x = 0.145945
y = y = 0.045217
Y = y = 0.045217

We now have the 455 nm light specified in 3 different color spaces:

  • XYZ: (0.342957, 0.106256, 1.900700)
  • xyz: (0.145945, 0.045217, 0.808838)
  • xyY: (0.145945, 0.045217, 0.045217)

So we've converted a wavelength of pure monochromatic emitted light into a XYZ color. Now we want to convert that to RGB.

How to convert XYZ into RGB?

XYZ, xyz, and xyY are absolute color spaces that describe colors using absolute physics.

Meanwhile, every practical color spaces that people use:

  • Lab
  • Luv
  • HSV
  • HSL
  • RGB

depends some whitepoint. The colors are then described as being relative to that whitepoint.

For example,

  • RGB white (255,255,255) means "white"
  • Lab white (100, 0, 0) means "white"
  • LCH white (100, 0, 309) means "white"
  • HSL white (240, 0, 100) means "white"
  • HSV white (240, 0, 100) means "white"

But there is no such color as white. How do you define white? The color of sunlight?

  • at what time of day?
  • with how much cloud cover?
  • at what latitude?
  • on Earth?

Some people use the white of their (horribly orange) incandescent bulbs to mean white. Some people use the color of their florescent lights. There is no absolute physical definition of white - white is in our brains.

So we have to pick a white

We have to pick a white. (Really you have to pick a white.) And there are plenty of whites to choose from:

  • Illuminant A: kinda like tungsten lamp enter image description here
  • Illuminant B&C: trying to fake noon sunlight by putting filters in front of tungsten lamp enter image description here
  • Illuminant D50: 5000K natural daylight enter image description here
  • Illuminant D55: 5500K natural daylight enter image description here
  • Illuminant D65: 6504K natural daylight enter image description here
  • Illuminant D75: 7500K natural daylight enter image description here
  • Illuminant E: theoretical of all colors equally present enter image description here
  • Illuminant F: fluorescent lights enter image description here (FL8)
  • Illuminant L: LED lighting

I will pick a white for you. The same white that sRGB uses:

  • D65 - daylight illumination of clear summer day in northern Europe

D65 (which has a color close to 6504K, but not quite because of the Earth's atmosphere), has a color of:

  • XYZ_D65: (0.95047, 1.00000, 1.08883)

With that, you can convert your XYZ into Lab (or Luv) - a color-space equally capable of expressing all theoretical colors. And now we have a 4th color space representation of our 445 nm monochromatic emission of light:

  • XYZ: (0.342957, 0.106256, 1.900700)
  • xyz: (0.145945, 0.045217, 0.808838)
  • xyY: (0.145945, 0.045217, 0.045217)
  • Lab: (38.94259, 119.14058, -146.08508) (assuming d65)

But you want RGB

Lab (and Luv) are color spaces that are relative to some white-point. Even though you were forced to pick an arbitrary white-point, you can still represent every possible color.

RGB is not like that. With RGB:

  • not only is the color relative to some white-point
  • but is is also relative to three primary colors: red, green, blue

If you specify an RGB color of (255, 0, 0), you are saying you want "just red". But there is no definition of red. There is no such thing as "red", "green", or "blue". The rainbow is continuous, and doesn't come with an arrow saying:

This is red

And again this means we have to pick three pick three primary colors. You have to pick your three primary colors to say what "red", "green", and "blue" are. And again you have many different defintions of Red,Green,Blue to choose from:

  • CIE 1931
  • ROMM RGB
  • Adobe Wide Gamut RGB
  • DCI-P3
  • NTSC (1953)
  • Apple RGB
  • sRGB
  • Japanese NTSC
  • PAL/SECAM
  • Adobe RGB 98
  • scRGB

I'll pick for you. I'll pick these three colors:

  • Red: xyY = (0.6400, 0.3300, 0.2126)
  • Green: xyY = (0.3000, 0.6000, 0.7152)
  • Blue: xyY = (0.1500, 0.0600, 0.0722)

Those were also the primaries chosen for by an international committee in 1996.

They created a standard that said everyone should use:

  • Whitepoint: D65 daylight
  • Red: (0.6400, 0.3300, 0.2126)
  • Green: (0.3000, 0.6000, 0.7152)
  • Blue: (0.1500, 0.0600, 0.0722)

And they called that standard sRGB.

The final push

Now that we have chosen our

  • white-point
  • three primaries

we can now convert you XYZ color into RGB:

  • RGB = (1.47450, -178.21694, 345.59392)

Unfortunately there are some problems with that RGB value:

  • your monitor cannot display negative green (-178.21694); that means it's a color outside what your monitor can display.
  • your monitor cannot display more blue than 255 (345.59392); the monitor only only be as blue as the blue is - it can't get any bluer. That means it's a color outside what your monitor can display.

So we have to round:

  • XYZ: (0.342957, 0.106256, 1.900700)
  • xyz: (0.145945, 0.045217, 0.808838)
  • xyY: (0.145945, 0.045217, 0.045217)
  • Lab: (38.94259, 119.14058, -146.08508) (d65)
  • RGB: (1, 0, 255) (sRGB)

And now we have the closest approximation sRGB of wavelength 455 nm of light:

enter image description here

Solution 4:

There is a relationship between frequency and what is known as Hue, but for complicated reasons of perception, monitor gamut, and calibration, the best you can achieve outside of expensive lab equipment is a gross approximation.

See http://en.wikipedia.org/wiki/HSL_and_HSV for the math, and note that you'll have to come up with your best guess for the Hue ⇔ Frequency mapping. I expect this empirical mapping to be anything but linear.

Solution 5:

I think the answers fail to address a problem with the actual question.

RGB values are generally derived from the XYZ color space which is the combination of a standard human observer function, an illuminate and the relative power of the sample at each wavelength over the range of ~360-830.

I'm not sure of what you are trying to achieve here but it would be possible to calculate a relatively "accurate" RGB value for a sample where each discrete band of the spectrum @ say 10nm was fully saturated. The transform looks like this Spectrum ->XYZ->RGB. Check out Bruce Lindbloom's site for the math. From the XYZ you can also easily calculate hue, chroma or colorimetric values such as L*a*b*.