Should one have the desire, it is possible to map a wavelength from the visible light spectrum into an RGB color for display on a monitor. You can also [[Relationship Between Wavelength and Frequency|derive the wavelength from the frequency]] and vice versa. # Notability I went down this rabbit hole while working on a set of CSS snippets to make [[CRT Monitor Styled Codeblocks using CSS]] based on [[CRT Phosphor Wavelengths]]. # Conversion ## Kuntzleman-Jacobson Technique The [Kuntzleman-Jacobson technique](https://pubs.acs.org/doi/10.1021/acs.jchemed.5b00844) was designed for spectrophotometry using modern smartphones. Given the smooth curve from short to long wavelengths I am inclined to believe that it is relatively representative. However, the colors themselves are less pleasing than other techniques and don't always convey the feel that it seems that they should - though this may be due to bias or influence of other factors such as glass and impurities that I have observed in older display technologies. ## Spektre Technique Here is some code to convert wavelengths to colors, based on spectral curves derived from data from the sun. This is of course only an approximation. Compared to the Kuntzleman-Jacobson Technique, there is a much stronger transition around 550nm where it switches abruptly from greenish to orange-red instead of passing through yellow. And while it is clear that it takes into consideration some human eye effects by consequence of matching the curve against an RGB color gradient, it does not make an effort to analyze it in terms of color space profiles actually used by display technologies nor that of the actual responses of the human eye. That said, this is amazing work, and does not require massive tables of data to get a result. ```cardlink url: https://stackoverflow.com/a/22681410/1255156 title: "RGB values of visible spectrum" description: "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?..." host: stackoverflow.com image: https://cdn.sstatic.net/Sites/stackoverflow/Img/[email protected]?v=73d79a89bded ``` ### C Implementation ```c 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); } } ``` ### Javascript Implementation Here is also a complete Javascript implementation of the above, which is used to generate a smooth spectrum gradient. ```javascript 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('stop-color', 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" ); } ``` ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <script src="js/spectrum.js"></script> </head> <body onload="spectrogram();"> </body> </html> ``` ## Bobtato's Modified Spektre Technique ```cardlink url: https://stackoverflow.com/a/75727053/1255156 title: "RGB values of visible spectrum" description: "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?..." host: stackoverflow.com image: https://cdn.sstatic.net/Sites/stackoverflow/Img/[email protected]?v=73d79a89bded ``` An alternative using degree 2 b-splines using a lookup table based on Spektre's work has also been proposed. This strategy makes the code much simpler and less resource intensive. ```javascript function wavelengthToRGB (λ) { const C=[ 350, 3.08919e-5,-2.16243e-2, 3.78425e+0, 0.00000e+0, 0.00000e+0, 0.00000e+0, 4.33926e-5,-3.03748e-2, 5.31559e+0, 397, -5.53952e-5, 4.68877e-2,-9.81537e+0, 6.13203e-5,-4.86883e-2, 9.66463e+0, 4.41410e-4,-3.46401e-1, 6.80468e+1, 423, -3.09111e-5, 2.61741e-2,-5.43445e+0, 1.85633e-4,-1.53857e-1, 3.19077e+1, -4.58520e-4, 4.14940e-1,-9.29768e+1, 464, 2.86786e-5,-2.91252e-2, 7.39499e+0, -1.66581e-4, 1.72997e-1,-4.39224e+1, 4.37994e-7,-1.09728e-2, 5.83495e+0, 514, 2.06226e-4,-2.11644e-1, 5.43024e+1, -6.65652e-5, 7.01815e-2,-1.74987e+1, 9.41471e-5,-1.07306e-1, 3.05925e+1, 565, -2.78514e-4, 3.36113e-1,-1.00439e+2, -1.79851e-4, 1.98194e-1,-5.36623e+1, 1.12142e-5,-1.35916e-2, 4.11826e+0, 606, -1.44403e-4, 1.73570e-1,-5.11884e+1, 2.47312e-4,-3.19527e-1, 1.03207e+2, 0.00000e+0, 0.00000e+0, 0.00000e+0, 646, 6.24947e-5,-9.37420e-2, 3.51532e+1, 0.00000e+0, 0.00000e+0, 0.00000e+0, 0.00000e+0, 0.00000e+0, 0.00000e+0, 750 ]; let [r,g,b] = [0,0,0]; if (λ >= C[0] && λ < C[C.length-1]) { for (let i=0; i<C.length; i+=10) { if (λ < C[i+10]) { const λ2 = λ*λ; r = C[i+1]*λ2 + C[i+2]*λ + C[i+3]; g = C[i+4]*λ2 + C[i+5]*λ + C[i+6]; b = C[i+7]*λ2 + C[i+8]*λ + C[i+9]; break; } } } return [r,g,b]; } ``` By including some of the helpers from the JS version of Spektre we can use it like this: ```javascript [440,502,505,506,524,525,539,558,600,602,606].map( function(wlnm){ return rgb2hex(...wavelengthToRGB(wlnm).map( function(element){ return Math.round(element * 255) } )) } ) ``` ## CIE Table > The International Commission on Illumination (usually abbreviated CIE for its French name, Commission internationale de l'éclairage) is the international authority on light, illumination, colour, and colour spaces. It was established in 1913 as a successor to the Commission Internationale de Photométrie, which was founded in 1900, and is today based in Vienna, Austria. \- via [Wikipedia](https://en.wikipedia.org/wiki/International_Commission_on_Illumination) The CIE [publishes](https://web.archive.org/web/20170712203603/http://cie.mogi.bme.hu/cie_arch/kee/div1/tc148.pdf) tables of data on colors. Honestly, they are pretty annoying to use without learning their equations and what all their mathematical symbols mean, so I will figure this out later if ever. # Color Spaces ## XYZ Color Space ## OKLAB See also this video by [[Acerola]] on color spaces, XYZ color space, and OKLAB colorspace: https://youtu.be/fv-wlo8yVhk