RGB values of visible spectrum
AlgorithmRgbAlgorithm Problem Overview
Algorithm Solutions
Solution 1  Algorithm
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:
 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:
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=(l400.0)/(410.0400.0); r= +(0.33*t)(0.20*t*t); }
else if ((l>=410.0)&&(l<475.0)) { t=(l410.0)/(475.0410.0); r=0.14 (0.13*t*t); }
else if ((l>=545.0)&&(l<595.0)) { t=(l545.0)/(595.0545.0); r= +(1.98*t)( t*t); }
else if ((l>=595.0)&&(l<650.0)) { t=(l595.0)/(650.0595.0); r=0.98+(0.06*t)(0.40*t*t); }
else if ((l>=650.0)&&(l<700.0)) { t=(l650.0)/(700.0650.0); r=0.65(0.84*t)+(0.20*t*t); }
if ((l>=415.0)&&(l<475.0)) { t=(l415.0)/(475.0415.0); g= +(0.80*t*t); }
else if ((l>=475.0)&&(l<590.0)) { t=(l475.0)/(590.0475.0); g=0.8 +(0.76*t)(0.80*t*t); }
else if ((l>=585.0)&&(l<639.0)) { t=(l585.0)/(639.0585.0); g=0.84(0.84*t) ; }
if ((l>=400.0)&&(l<475.0)) { t=(l400.0)/(475.0400.0); b= +(2.20*t)(1.50*t*t); }
else if ((l>=475.0)&&(l<560.0)) { t=(l475.0)/(560.0475.0); b=0.7 ( t)+(0.30*t*t); }
}
//
Where

l
is the wavelength in [nm] usable valueas arel = < 400.0 , 700.0 >

r,g,b
are returning color components in range< 0.0 , 1.0 >
Solution 2  Algorithm
To convert a wavelength into an RGB color
First you consult a CIE 1964 Supplementary Standard Colorimetric Observer chart (archive)
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:
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
 Illuminant B&C: trying to fake noon sunlight by putting filters in front of tungsten lamp
 Illuminant D50: 5000K natural daylight
 Illuminant D55: 5500K natural daylight
 Illuminant D65: 6504K natural daylight
 Illuminant D75: 7500K natural daylight
 Illuminant E: theoretical of all colors equally present
 Illuminant F: fluorescent lights (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 colorspace 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 whitepoint. Even though you were forced to pick an arbitrary whitepoint, you can still represent every possible color.
RGB is not like that. With RGB:
 not only is the color relative to some whitepoint
 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
 DCIP3
 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
 whitepoint
 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:
Solution 3  Algorithm
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 4  Algorithm
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  Algorithm
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 ~360830.
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*
.
Solution 6  Algorithm
If you want an exact match then the only solution is to perform a convolution of the x,y,z color matching functions with your spectral values so you finally get a (deviceindependent) XYZ color representation that you can later convert into (devicedependent) RGB.
This is described here: http://www.cs.rit.edu/~ncs/color/t_spectr.html
You can find the x,y,z color matching function for convolution here: http://cvrl.ioo.ucl.ac.uk/cmfs.htm
Solution 7  Algorithm
This is most of what color profiles deal with. Basically, for a given device (scanner, camera, monitor, printer, etc.) a color profile tells what actual colors of light will be produced by a specific set of inputs.
Also note that for most real devices, you only deal with a few discrete wavelengths of light, and intermediate colors are produced not by producing that wavelength directly, but by mixing varying amounts of the two neighboring wavelengths that are available. Given that we perceive color in the same way, that's not really a problem, but depending on why you care, it may be worth knowing anyway.
Without a color profile (or equivalent information) you lack the information necessary to map RGB value to colors. An RGB value of pure red will normally map to the reddest color that device is capable of producing/sensing (and likewise, pure blue to the bluest color, etc.)  but that reddest or bluest can and will vary (widely) based on the device (and in some cases not just the device itself eithere.g., switching inks in a printer can change the characteristics).
Solution 8  Algorithm
Patapom has it almost right: for each wavelength you calculate the CIE XYZ values, then convert those to (say) sRGB using standard formulas (if you're lucky you'll find code you can just use to do this conversion). So the key step is getting the XYZ values. Fortunately, for singlewavelength light this is easy: the XYZ color matching functions are simply tables listing the XYZ values for a given wavelength. So just look it up. If you had light with a more complicated spectrum, maybe a black body, then you'd have to average the XYZ responses times the amount of each wavelength in the light.
Solution 9  Algorithm
I'm not a programmer. I'm not a physician. I'm just a musician who has two eyes (as any human being has).
So... I know the electromagnetic waves have a logarythm scale pattern into the Radio and TV range. Why it should be differente in the visible light range?
Inside the Radio and TV world we use a simple equation: We divide a given range between two frecuencies into a given number of parts according to the ratio between the extreme frecuencies.
Let's say: If our range starts in 100 MHZ and ends in 200 MHZ, we have a ratio of 2 (200 is equal to 100 multiplied by 2).
So, if we have to divide that range in 10 equal parts, we have to use this equation:
The first frecuency (100 MHZ) multiplied by the 10th root of 2.
That new value multiplied by the 10th root of 2.
And so and on.
Why we use the 10th root of 2? Simple: Remember it is not a linear scale, it is a logarythm scale (exactly the same of musical notes).
So, based in that equation, as we know the visible light spectrum is between 780 and 380 nanometers (aproximately 384.02 THZ and 789.26 THZ; both values very aproximately, because it is a variable value according to the individual optical capability), we just to know the ratio between those frecuencies:
789.26/384.02=2,055
Also, we know that the RGB aproximately equivalent to that extreme frecuencies are:
384.02 THZ = 95,0,0 (hx=5F0000)
788.92 THZ = 97,0,97 (hx=610061)
Also, we know all the possible RGB combinations between that points are = 1595
So, with all those values we have a simple equation:
RGB = 95, 0, 0 = 384.02 THZ
RGB = 96, 0, 0 = 384.02 THZ multiplied by (1595th root of 2,055) = 384,19 THZ
RGB = 97, 0, 0 = 384,19 THZ multiplied by (1595th root of 2,055) = 384.37 THZ
And so and on
Simple scale.
Just my humble opinion.
Solution 10  Algorithm
VBA code is derived from Approximate "RGB values for Visible Wavelengths" by Dan Bruton ([email protected]). Link to his original Fortran code: http://www.physics.sfasu.edu/astro/color/spectra.html Spectra program: http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm
Sub Wavelength_To_RGB()
'Purpose: Loop thru the wavelengths in the visible spectrum of light
' and output the RGB values and colors to a worksheet.
' Wavelength range: 380nm and 780nm
Dim j As Long, CellRow As Long
Dim R As Double, G As Double, B As Double
Dim iR As Integer, iG As Integer, iB As Integer
Dim WL As Double
Dim Gamma As Double
Dim SSS As Double
Gamma = 0.8
CellRow = 1
For j = 380 To 780
WL = j
Select Case WL
Case 380 To 440
R = (WL  440#) / (440#  380#)
G = 0#
B = 1#
Case 440 To 490
R = 0#
G = ((WL  440#) / (490#  440#))
B = 1#
Case 490 To 510
R = 0#
G = 1#
B = ((WL  510#) / (510#  490#))
Case 510 To 580
R = ((WL  510#) / (580#  510#))
G = 1#
B = 0#
Case 580 To 645
R = 1#
G = ((WL  645#) / (645#  580#))
B = 0#
Case 645 To 780
R = 1#
G = 0#
B = 0#
Case Else
R = 0#
G = 0#
B = 0#
End Select
'LET THE INTENSITY SSS FALL OFF NEAR THE VISION LIMITS
If WL > 700 Then
SSS = 0.3 + 0.7 * (780#  WL) / (780#  700#)
ElseIf WL < 420 Then
SSS = 0.3 + 0.7 * (WL  380#) / (420#  380#)
Else
SSS = 1#
End If
'GAMMA ADJUST
R = (SSS * R) ^ Gamma
G = (SSS * G) ^ Gamma
B = (SSS * B) ^ Gamma
'Multiply by 255
R = R * 255
G = G * 255
B = B * 255
'Change RGB data type from Double to Integer.
iR = CInt(R)
iG = CInt(G)
iB = CInt(B)
'Output to worksheet
Cells(CellRow, 1).Interior.Color = RGB(iR, iG, iB)
Cells(CellRow, 2) = WL
Cells(CellRow, 3) = "(" & iR & "," & iG & "," & iB & ")"
CellRow = CellRow + 1
Next j
End Sub
Solution 11  Algorithm
A runnable example based on a popular answer:
function spectrogram() {
var svgns = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgns, 'svg');
var defs = document.createElementNS(svgns, 'defs');
var gradient = document.createElementNS(svgns, 'linearGradient');
var rect = document.createElementNS(svgns, 'rect');
var stops = spectral_gradient( 400, 700, 3 );
for( var i = 0, length = stops.length; i < length; i++ ) {
var stop = document.createElementNS(svgns, 'stop');
stop.setAttribute('offset', stops[i].offset);
stop.setAttribute('stopcolor', stops[i].color);
gradient.appendChild(stop);
}
// Apply the <lineargradient> to <defs>
gradient.id = 'Gradient';
gradient.setAttribute('x1', '0');
gradient.setAttribute('x2', '1');
gradient.setAttribute('y1', '0');
gradient.setAttribute('y2', '0');
defs.appendChild(gradient);
// Setup the <rect> element.
rect.setAttribute('fill', 'url(#Gradient)');
rect.setAttribute('width', '100%');
rect.setAttribute('height', '100%');
// Assign an id, classname, width and height
svg.setAttribute('width', '100%');
svg.setAttribute('height', '100%')
svg.setAttribute('version', '1.1');
svg.setAttribute('xmlns', svgns);
// Add the <defs> and <rect> elements to <svg>
svg.appendChild(defs);
svg.appendChild(rect);
// Add the <svg> element to <body>
document.body.appendChild(svg);
}
function spectral_gradient( wl1, wl2, steps ) {
var stops = [];
var delta = Math.abs( wl2  wl1 );
for( var wl = wl1; wl <= wl2; wl += steps ) {
var offset = Math.round( (1  Math.abs( wl2  wl ) / delta) * 100 );
stops.push({
"color": wavelength2hex( wl ),
"offset": offset + "%"
});
}
return stops;
}
function wavelength2hex( l ) {
var wl = wavelength2rgb( l );
var rgb = {
"r": Math.round( wl.r * 255 ),
"g": Math.round( wl.g * 255 ),
"b": Math.round( wl.b * 255 )
};
return rgb2hex( rgb.r, rgb.g, rgb.b );
}
function wavelength2rgb( l ) {
var t;
var r = 0.0;
var g = 0.0;
var 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);
}
return {"r": r, "g": g, "b": b};
}
function rgb2hex( r, g, b ) {
return "#" + hex( r ) + hex( g ) + hex( b );
}
function hex( v ) {
return v.toString( 16 ).padStart( 2, "0" );
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf8" />
<script src="js/spectrum.js"></script>
</head>
<body onload="spectrogram();">
</body>
</html>