Converting Frequency to RGB

Chris R. Schoenenman
18 Nov 1991, comp.graphics.algorithms
The best way of finding the RGB values of a given wavelength is to use the CIE chromaticity diagram. The spectral locus is on the edge of the wing-shaped curve. You just need to transform the xy coordinate from CIEXYZ space into RGB space.

Below I have spectral locus coordinates from 380nm to 780m in steps of 5 nm. It's written as a C array. We'll refer to any particular wavelength's coordinates as xc and yc.

To transform into RGB, we need the xy coordinates of the RGB primaries and the alignment white. I'll use a set of typical values.

                  x       y
        Red     0.628   0.346           call these xr and yr
        Green   0.268   0.588            "     "   xg and yg
        Blue    0.150   0.070            "     "   xb and yb
        White   0.313   0.329            "     "   xw and yw
From these we can compute the linear transformation from CIEXYZ to RGB. It is:
        [R] = [  2.739 -1.145 -0.424 ] [X]
        [G] = [ -1.119  2.029  0.033 ] [Y]
        [B] = [  0.138 -0.333  1.105 ] [Z]
However, if the xy coordinate of the spectral color isn't inside the triangle formed by the RGB xy coordinates given above (and none of them are), then we must first find a closest match that is inside the triangle. Otherwise our RGB values will go outside the range [0,1], i.e. some values will be less than zero or greater than one.

One common way of moving the color inside the triangle is to make the color whiter (i.e. desaturate it). Mathematically we do this by intersecting the line joining the color and the alignment white with the edges of the triangle.

First define the line that goes through white and your color implicitly:

        Lc:     [-(yw - yc)] * P + [-(yw - yc)] * [xc]
                [  xw - wc ]       [  xw - xc ]   [yc]
For every pair of red, green, and blue, define an explicit (parametric line). For example, the line between the red and green primaries is:
        Lrg:    P = [xr] + t * [xg - xr]
                    [yr]       [yg - yr]
Substitute Lrg for P into Lc and solve for the parameter t. If 0 <= t <= 1, then you've got the answer and you don't need to try the other lines. Otherwise, substitute Lgb, and if necessary Lbr. If t is not in the range [0,1] then the intersection occured outside the triangle.

Once you have t, plug it into the original equation for Lrg (or whatever line gave a good solution) and you get the xy coordinate. Plug this into the linear transformation above (using z = 1 - x - y) to get the RGB values. At least one of R, G, or B should be exactly one (i.e. fully saturated in the RGB color space).

Since this is an involved procedure, I would suggest precomputing RGB's for all the wavelengths you'll need (or enough to interpolate) once for each display device you'll be using. If you know the chromaticity coordinates of your display's primaries and its white you can compute the linear transformation easily. Just check reference [1] or if you can't get it, just say so and I'll give you the equations. [1] also gives code to transform RGB to HSV and back.

Rogers, David F., "Procedural Elements for Computer Graphics," McGraw-Hill Book Company, New York, 1985, pp390-398.
/*
 * CIE 1931 chromaticity coordinates of spectral stimuli
 * (only xy,  z = 1 - x - y)
 */
float   spectrum_xy[][2] = {
    {0.1741, 0.0050}, {0.1740, 0.0050}, {0.1738, 0.0049}, {0.1736, 0.0049},
    {0.1733, 0.0048}, {0.1730, 0.0048}, {0.1726, 0.0048}, {0.1721, 0.0048},
    {0.1714, 0.0051}, {0.1703, 0.0058}, {0.1689, 0.0069}, {0.1669, 0.0086},
    {0.1644, 0.0109}, {0.1611, 0.0138}, {0.1566, 0.0177}, {0.1510, 0.0227},
    {0.1440, 0.0297}, {0.1355, 0.0399}, {0.1241, 0.0578}, {0.1096, 0.0868},
    {0.0913, 0.1327}, {0.0687, 0.2007}, {0.0454, 0.2950}, {0.0235, 0.4127},
    {0.0082, 0.5384}, {0.0039, 0.6548}, {0.0139, 0.7502}, {0.0389, 0.8120},
    {0.0743, 0.8338}, {0.1142, 0.8262}, {0.1547, 0.8059}, {0.1929, 0.7816},
    {0.2296, 0.7543}, {0.2658, 0.7243}, {0.3016, 0.6923}, {0.3373, 0.6589},
    {0.3731, 0.6245}, {0.4087, 0.5896}, {0.4441, 0.5547}, {0.4788, 0.5202},
    {0.5125, 0.4866}, {0.5448, 0.4544}, {0.5752, 0.4242}, {0.6029, 0.3965},
    {0.6270, 0.3725}, {0.6482, 0.3514}, {0.6658, 0.3340}, {0.6801, 0.3197},
    {0.6915, 0.3083}, {0.7006, 0.2993}, {0.7079, 0.2920}, {0.7140, 0.2859},
    {0.7190, 0.2809}, {0.7230, 0.2770}, {0.7260, 0.2740}, {0.7283, 0.2717},
    {0.7300, 0.2700}, {0.7311, 0.2689}, {0.7320, 0.2680}, {0.7327, 0.2673},
    {0.7334, 0.2666}, {0.7340, 0.2660}, {0.7344, 0.2656}, {0.7346, 0.2654},
    {0.7347, 0.2653}, {0.7347, 0.2653}, {0.7347, 0.2653}, {0.7347, 0.2653},
    {0.7347, 0.2653}, {0.7347, 0.2653}, {0.7347, 0.2653}, {0.7347, 0.2653},
    {0.7347, 0.2653}, {0.7347, 0.2653}, {0.7347, 0.2653}, {0.7347, 0.2653},
    {0.7347, 0.2653}, {0.7347, 0.2653}, {0.7347, 0.2653}, {0.7347, 0.2653},
    {0.7347, 0.2653}};