{"id":4115,"date":"2020-07-20T07:00:45","date_gmt":"2020-07-20T11:00:45","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=4115"},"modified":"2020-08-18T09:12:25","modified_gmt":"2020-08-18T13:12:25","slug":"making-color-spectrum-plots-part-2","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2020\/07\/20\/making-color-spectrum-plots-part-2\/","title":{"rendered":"Making Color Spectrum Plots &#8211; Part 2"},"content":{"rendered":"<div class=\"content\"><!--introduction--><p>It was a while ago now, but on <a href=\"https:\/\/blogs.mathworks.com\/steve\/files\/illuminant-d65.png\">April 27<\/a> I started explaining how I made this plot, which is from <a href=\"http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\"><b>DIPUM3E<\/b><\/a> (<i>Digital Image Processing Using MATLAB<\/i>, 3rd ed.):<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/illuminant-d65.png\" alt=\"\"> <\/p><p>In today's follow-up, I'll discuss how I computed the colors of the spectrum to display below the x-axis. I will use and refer to several <a href=\"http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\"><b>DIPUM3E<\/b><\/a> functions. These are available to you in <i>MATLAB Color Tools<\/i> on the <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/64161-matlab-color-tools\">File Exchange<\/a> and on <a href=\"https:\/\/github.com\/mathworks\/matlab-color-tools\">GitHub<\/a>. The entire set of <a href=\"http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\"><b>DIPUM3E<\/b><\/a> functions is also on <a href=\"https:\/\/github.com\/dipum\/dipum-toolbox\">GitHub<\/a>.<\/p><!--\/introduction--><h3>Contents<\/h3><div><ul><li><a href=\"#d9600026-f34d-4711-9e13-49481ca1ebe7\">Visible wavelength<\/a><\/li><li><a href=\"#1ef197ec-dc55-4afd-a291-d837a4630062\">Find XYZ values for the spectral colors<\/a><\/li><li><a href=\"#bcc8da43-e1db-46d2-a272-166f0e515110\">Try a simple XYZ to RGB conversion<\/a><\/li><li><a href=\"#4a23511a-a751-4aaf-bc9b-d4ba1df952a7\">Work with linear RGB values<\/a><\/li><li><a href=\"#bcb7e3e2-e1a3-4fd3-bd45-74e97f6ff698\">Heuristic scaling of linear RGB values<\/a><\/li><li><a href=\"#0f8df5c9-7246-4367-910c-972c66507ba0\">Smooth the curves<\/a><\/li><li><a href=\"#2a26c11a-c0a6-4fc1-acbc-b358c5681c7d\">Convert to nonlinear RGB values for the final result<\/a><\/li><li><a href=\"#9352d774-9f8a-486e-b7a5-18bf1f978583\">Utility functions<\/a><\/li><\/ul><\/div><h4>Visible wavelength<a name=\"d9600026-f34d-4711-9e13-49481ca1ebe7\"><\/a><\/h4><p>What should the x-axis limits be on this plot? In other words, what is the visible wavelength that we are interested in? You will see different limits being used in different places. The limits used here, 380 nm to 780 nm, are those given in Berns, R. S. (2000). <i>Billmeyer and Saltzman's Principals of Color Technology<\/i>, 3rd ed., John Wiley &amp; Sons, NJ.<\/p><pre class=\"codeinput\">lambda = 380:780;\r\n<\/pre><h4>Find XYZ values for the spectral colors<a name=\"1ef197ec-dc55-4afd-a291-d837a4630062\"><\/a><\/h4><p>The first computational step is to find the XYZ values for each value of lambda. This computation can be found in the <a href=\"http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\"><b>DIPUM3E<\/b><\/a> function <tt>lambda2xyz<\/tt>. But it is really simple: just interpolate into the CIE XYZ color matching functions, which are returned by the <a href=\"http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\"><b>DIPUM3E<\/b><\/a> function <tt>colorMatchingFunctions<\/tt>.<\/p><pre class=\"codeinput\">T = colorMatchingFunctions;\r\nhead(T)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n  8&times;4 table\r\n\r\n    lambda        x             y             z     \r\n    ______    __________    __________    __________\r\n\r\n     360       0.0001299     3.917e-06     0.0006061\r\n     361      0.00014585    4.3936e-06    0.00068088\r\n     362       0.0001638    4.9296e-06    0.00076515\r\n     363        0.000184    5.5321e-06    0.00086001\r\n     364      0.00020669    6.2082e-06    0.00096659\r\n     365       0.0002321     6.965e-06      0.001086\r\n     366      0.00026073    7.8132e-06     0.0012206\r\n     367      0.00029308    8.7673e-06     0.0013727\r\n\r\n<\/pre><p>To find the XYZ values for a specific lambda, such as 500, we can use <tt>interp1<\/tt>:<\/p><pre class=\"codeinput\">interp1(T.lambda,[T.x,T.y,T.z],500)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n    0.0049    0.3230    0.2720\r\n\r\n<\/pre><p>Or we can find the XYZ values for all of the wavelengths we are interested in.<\/p><pre class=\"codeinput\">XYZ = interp1(T.lambda,[T.x,T.y,T.z],lambda(:));\r\n<\/pre><pre class=\"codeinput\">plot(lambda(:),XYZ)\r\ntitle(<span class=\"string\">\"XYZ values for spectral wavelengths\"<\/span>)\r\nlegend(<span class=\"string\">\"X\"<\/span>,<span class=\"string\">\"Y\"<\/span>,<span class=\"string\">\"Z\"<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_01.png\" alt=\"\"> <h4>Try a simple XYZ to RGB conversion<a name=\"bcc8da43-e1db-46d2-a272-166f0e515110\"><\/a><\/h4><p>Let's try simply converting these XYZ values directly to RGB using the Image Processing Toolbox function <tt>xyz2rgb<\/tt>.<\/p><pre class=\"codeinput\">RGB = xyz2rgb(XYZ);\r\ndrawColorbar(RGB)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_02.png\" alt=\"\"> <p>(The function <tt>drawColorbar<\/tt> is below. It uses the <a href=\"http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\"><b>DIPUM3E<\/b><\/a> function <tt>colorSwatches<\/tt>.)<\/p><p>That doesn't look like a very good spectral color plot to me. It seems uneven, with several patches that seem to be mostly one color. What's going on with this? We can see the problem if we draw a line plot of the RGB values. (The <tt>plotrgb<\/tt> and <tt>shadeGamutRegion<\/tt> functions are down below.)<\/p><pre class=\"codeinput\">close\r\nplotrgb(lambda(:),RGB)\r\nshadeGamutRegion\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_03.png\" alt=\"\"> <p>The gray shaded region shows the range between 0 and 1; this is the displayable range of colors. Everything outside that range (negative values, or values greater than 1), can't be displayed exactly. These out-of-range values are being clipped to the displayable range, and that leads to a bad results.<\/p><p>I'm going to show you the method used by the <a href=\"http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\"><b>DIPUM3E<\/b><\/a> function <tt>spectrumColors<\/tt>. The method is a variation of the one described in: Andrew Young (2012). <i>Rendering Spectra<\/i> (<a href=\"https:\/\/aty.sdsu.edu\/explain\/optics\/rendering.html\">https:\/\/aty.sdsu.edu\/explain\/optics\/rendering.html<\/a>). Retrieved July 16, 2020.<\/p><h4>Work with linear RGB values<a name=\"4a23511a-a751-4aaf-bc9b-d4ba1df952a7\"><\/a><\/h4><p>First, let's convert the XYZ values to \"linear\" RGB values. The typical RGB values we see for image pixels are related nonlinearly to light intensity, and linear RGB values are more appropriate for the following averaging and scaling steps. The Image Processing Toolbox function <tt>xyz2rgb<\/tt> can optionally convert to linear values.<\/p><pre class=\"codeinput\">RGB_lin = xyz2rgb(XYZ,<span class=\"string\">'ColorSpace'<\/span>,<span class=\"string\">'linear-rgb'<\/span>);\r\nplotrgb(lambda(:),RGB_lin)\r\ntitle(<span class=\"string\">\"Linear RGB values for spectral wavelengths\"<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_04.png\" alt=\"\"> <h4>Heuristic scaling of linear RGB values<a name=\"bcb7e3e2-e1a3-4fd3-bd45-74e97f6ff698\"><\/a><\/h4><p>We want to modify those curves so that they fall within the range [0,1] and produce a reasonably accurate, smoothly varying, and attractive representation of the spectral colors.<\/p><p>The next thing we'll do is scale so that the maximum linear RGB value is 1.0. (Note: Young (2012) divides by a fixed value of 2.34.)<\/p><pre class=\"codeinput\">RGB_lin = RGB_lin \/ max(RGB_lin(:));\r\n\r\nplotrgb(lambda(:),RGB_lin)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_05.png\" alt=\"\"> <p>Now, one component at a time, and for each spectral color, mix in a sufficient amount neutral gray with the same Y to bring negative component values up to 0.<\/p><pre class=\"codeinput\">Y = XYZ(:,2);\r\n<span class=\"keyword\">for<\/span> k = 1:3\r\n   C = RGB_lin(:,k);\r\n   F = Y .\/ (Y - C);\r\n\r\n   <span class=\"comment\">% No scaling is needed for component values that are already<\/span>\r\n   <span class=\"comment\">% nonnegative.<\/span>\r\n   F(C &gt;= 0) = 1;\r\n\r\n   RGB_lin = Y + F.*(RGB_lin - Y);\r\n<span class=\"keyword\">end<\/span>\r\n\r\nplotrgb(lambda(:),RGB_lin)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_06.png\" alt=\"\"> <p>Next, to get brighter spectral colors, including a good yellow, scale up the linear RGB values, allowing them to get higher than 1.0. Then, for each color, scale all components back down, if necessary, so that the maximum component value is 1.0. Note: [Young 2012] uses a scale factor of 1.85.<\/p><pre class=\"codeinput\">RGB_lin = RGB_lin * 2.5;\r\nS = max(RGB_lin,[],2);\r\nS = max(S,1);\r\nRGB_lin = RGB_lin .\/ S;\r\n\r\nplotrgb(lambda(:),RGB_lin)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_07.png\" alt=\"\"> <h4>Smooth the curves<a name=\"0f8df5c9-7246-4367-910c-972c66507ba0\"><\/a><\/h4><p>Smooth out the linear RGB curves to eliminate discontinuous first derivatives. This helps the spectrum to appear smoother, reducing sharp transition points. Note: This step is not in [Young 2012].<\/p><pre class=\"codeinput\">RGB_lin = conv2(RGB_lin,ones(21,1)\/21,<span class=\"string\">'same'<\/span>);\r\nplotrgb(lambda(:),RGB_lin)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_08.png\" alt=\"\"> <p>Eliminate small negative numbers and numbers slightly greater than 1 that have been introduced through floating-point round-off.<\/p><pre class=\"codeinput\">RGB_lin = min(max(RGB_lin,0),1);\r\n<\/pre><h4>Convert to nonlinear RGB values for the final result<a name=\"2a26c11a-c0a6-4fc1-acbc-b358c5681c7d\"><\/a><\/h4><p>Convert to nonlinear sRGB values suitable for display on a computer monitor.<\/p><pre class=\"codeinput\">RGB = lin2rgb(RGB_lin);\r\n\r\nplotrgb(lambda(:),RGB)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_09.png\" alt=\"\"> <pre class=\"codeinput\">drawColorbar(RGB)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_10.png\" alt=\"\"> <p>Next time, I'll talk about how to draw the spectral color scale underneath the plot.<\/p><h4>Utility functions<a name=\"9352d774-9f8a-486e-b7a5-18bf1f978583\"><\/a><\/h4><pre class=\"codeinput\"><span class=\"keyword\">function<\/span> drawColorbar(rgb_colors)\r\nf = figure;\r\nf.Position(4) = f.Position(4) \/ 5;\r\ncolorSwatches(rgb_colors,0)\r\ndaspect([40 1 1])\r\naxis <span class=\"string\">off<\/span>\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">function<\/span> plotrgb(x,rgb_colors)\r\n<span class=\"comment\">% Pick the colors we want to use from the normal line color order.<\/span>\r\nc = lines(7);\r\nblue = c(1,:);\r\nred = c(2,:);\r\ngreen = c(5,:);\r\nclf\r\nplot(x,rgb_colors(:,1),<span class=\"string\">'Color'<\/span>,red);\r\nhold <span class=\"string\">on<\/span>\r\nplot(x,rgb_colors(:,2),<span class=\"string\">'Color'<\/span>,green);\r\nplot(x,rgb_colors(:,3),<span class=\"string\">'Color'<\/span>,blue)\r\nhold <span class=\"string\">off<\/span>\r\ngrid <span class=\"string\">on<\/span>\r\naxis <span class=\"string\">tight<\/span>\r\nyl = ylim;\r\nylim([min(yl(1)-0.05,-0.05) max(yl(2)+0.05,1.05)])\r\n\r\nlegend(<span class=\"string\">\"R\"<\/span>,<span class=\"string\">\"G\"<\/span>,<span class=\"string\">\"B\"<\/span>)\r\nxlabel(<span class=\"string\">\"wavelength (nm)\"<\/span>)\r\n\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">function<\/span> shadeGamutRegion\r\nxl = xlim;\r\nxx = [xl(1) xl(2) xl(2) xl(1) xl(1)];\r\nyy = [0 0 1 1 0];\r\np = patch(xx,yy,[0.5 0.5 0.5],<span class=\"string\">\"FaceAlpha\"<\/span>,0.1,<span class=\"keyword\">...<\/span>\r\n    <span class=\"string\">\"EdgeAlpha\"<\/span>,0.1,<span class=\"string\">\"HandleVisibility\"<\/span>,<span class=\"string\">\"off\"<\/span>);\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><script language=\"JavaScript\"> <!-- \r\n    function grabCode_ed0fb3806b5044c18980507c638fce3f() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='ed0fb3806b5044c18980507c638fce3f ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' ed0fb3806b5044c18980507c638fce3f';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2020 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_ed0fb3806b5044c18980507c638fce3f()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2020a<br><\/p><\/div><!--\r\ned0fb3806b5044c18980507c638fce3f ##### SOURCE BEGIN #####\r\n%%\r\n% It was a while ago now, but on\r\n% <https:\/\/blogs.mathworks.com\/steve\/files\/illuminant-d65.png April 27>\r\n% I started explaining how I made this plot, which is from\r\n% <http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\r\n% *DIPUM3E*> (_Digital Image Processing Using MATLAB_, 3rd ed.):\r\n%\r\n% <<https:\/\/blogs.mathworks.com\/steve\/files\/illuminant-d65.png>>\r\n%\r\n% In today's follow-up, I'll discuss how I computed the colors of the\r\n% spectrum to display below the x-axis. I will use and refer to several\r\n% <http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\r\n% *DIPUM3E*> functions. These are available to you in _MATLAB Color\r\n% Tools_ on the\r\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/64161-matlab-color-tools\r\n% File Exchange> and on <https:\/\/github.com\/mathworks\/matlab-color-tools\r\n% GitHub>. The entire set of\r\n% <http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\r\n% *DIPUM3E*> functions is also on\r\n% <https:\/\/github.com\/dipum\/dipum-toolbox GitHub>.\r\n%\r\n%% Visible wavelength\r\n% What should the x-axis limits be on this plot? In other words, what is\r\n% the visible wavelength that we are interested in? You will see\r\n% different limits being used in different places. The limits used here,\r\n% 380 nm to 780 nm, are those given in Berns, R. S. (2000). _Billmeyer\r\n% and Saltzman's Principals of Color Technology_, 3rd ed., John Wiley &\r\n% Sons, NJ.\r\n\r\nlambda = 380:780;\r\n\r\n%% Find XYZ values for the spectral colors\r\n% The first computational step is to find the XYZ values for each value\r\n% of lambda. This computation can be found in the\r\n% <http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\r\n% *DIPUM3E*> function |lambda2xyz|. But it is really simple: just\r\n% interpolate into the CIE XYZ color matching functions, which are\r\n% returned by the <http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\r\n% *DIPUM3E*> function |colorMatchingFunctions|.\r\n\r\nT = colorMatchingFunctions;\r\nhead(T)\r\n\r\n%%\r\n% To find the XYZ values for a specific lambda, such as 500, we can use\r\n% |interp1|:\r\n\r\ninterp1(T.lambda,[T.x,T.y,T.z],500)\r\n\r\n%%\r\n% Or we can find the XYZ values for all of the wavelengths we are\r\n% interested in.\r\nXYZ = interp1(T.lambda,[T.x,T.y,T.z],lambda(:));\r\n\r\n%%\r\n\r\nplot(lambda(:),XYZ)\r\ntitle(\"XYZ values for spectral wavelengths\")\r\nlegend(\"X\",\"Y\",\"Z\")\r\n\r\n%% Try a simple XYZ to RGB conversion\r\n% Let's try simply converting these XYZ values directly to RGB using the\r\n% Image Processing Toolbox function |xyz2rgb|.\r\n\r\nRGB = xyz2rgb(XYZ);\r\ndrawColorbar(RGB)\r\n\r\n%%\r\n% (The function |drawColorbar| is below. It uses the\r\n% <http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\r\n% *DIPUM3E*> function |colorSwatches|.)\r\n%\r\n% That doesn't look like a very good spectral color plot to me. It seems\r\n% uneven, with several patches that seem to be mostly one color. What's\r\n% going on with this? We can see the problem if we draw a line plot of\r\n% the RGB values. (The |plotrgb| and |shadeGamutRegion| functions are\r\n% down below.)\r\n\r\nclose\r\nplotrgb(lambda(:),RGB)\r\nshadeGamutRegion\r\n\r\n%%\r\n% The gray shaded region shows the range between 0 and 1; this is the\r\n% displayable range of colors. Everything outside that range (negative\r\n% values, or values greater than 1), can't be displayed exactly. These\r\n% out-of-range values are being clipped to the displayable range, and\r\n% that leads to a bad results.\r\n%\r\n% I'm going to show you the method used by the\r\n% <http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\r\n% *DIPUM3E*> function |spectrumColors|. The method is a variation of the\r\n% one described in: Andrew Young (2012). _Rendering Spectra_\r\n% (https:\/\/aty.sdsu.edu\/explain\/optics\/rendering.html). Retrieved July\r\n% 16, 2020.\r\n\r\n%% Work with linear RGB values\r\n% First, let's convert the XYZ values to \"linear\" RGB values. The\r\n% typical RGB values we see for image pixels are related nonlinearly to\r\n% light intensity, and linear RGB values are more appropriate for the\r\n% following averaging and scaling steps. The Image Processing Toolbox\r\n% function |xyz2rgb| can optionally convert to linear values.\r\n\r\nRGB_lin = xyz2rgb(XYZ,'ColorSpace','linear-rgb');\r\nplotrgb(lambda(:),RGB_lin)\r\ntitle(\"Linear RGB values for spectral wavelengths\")\r\n\r\n%% Heuristic scaling of linear RGB values\r\n% We want to modify those curves so that they fall within the range\r\n% [0,1] and produce a reasonably accurate, smoothly varying, and\r\n% attractive representation of the spectral colors.\r\n%\r\n% The next thing we'll do is scale so that the maximum linear RGB value\r\n% is 1.0. (Note: Young (2012) divides by a fixed value of 2.34.)\r\nRGB_lin = RGB_lin \/ max(RGB_lin(:));\r\n\r\nplotrgb(lambda(:),RGB_lin)\r\n\r\n%%\r\n% Now, one component at a time, and for each spectral color, mix in a\r\n% sufficient amount neutral gray with the same Y to bring negative\r\n% component values up to 0.\r\nY = XYZ(:,2);\r\nfor k = 1:3\r\n   C = RGB_lin(:,k);\r\n   F = Y .\/ (Y - C);\r\n   \r\n   % No scaling is needed for component values that are already\r\n   % nonnegative.\r\n   F(C >= 0) = 1;\r\n   \r\n   RGB_lin = Y + F.*(RGB_lin - Y);\r\nend\r\n\r\nplotrgb(lambda(:),RGB_lin)\r\n\r\n%%\r\n% Next, to get brighter spectral colors, including a good yellow, scale\r\n% up the linear RGB values, allowing them to get higher than 1.0. Then,\r\n% for each color, scale all components back down, if necessary, so that\r\n% the maximum component value is 1.0. Note: [Young 2012] uses a scale\r\n% factor of 1.85.\r\n\r\nRGB_lin = RGB_lin * 2.5;\r\nS = max(RGB_lin,[],2);\r\nS = max(S,1);\r\nRGB_lin = RGB_lin .\/ S;\r\n\r\nplotrgb(lambda(:),RGB_lin)\r\n\r\n%% Smooth the curves\r\n% Smooth out the linear RGB curves to eliminate discontinuous first\r\n% derivatives. This helps the spectrum to appear smoother, reducing\r\n% sharp transition points. Note: This step is not in [Young 2012].\r\nRGB_lin = conv2(RGB_lin,ones(21,1)\/21,'same');\r\nplotrgb(lambda(:),RGB_lin)\r\n\r\n%%\r\n% Eliminate small negative numbers and numbers slightly greater than 1\r\n% that have been introduced through floating-point round-off.\r\nRGB_lin = min(max(RGB_lin,0),1);\r\n\r\n%% Convert to nonlinear RGB values for the final result\r\n% Convert to nonlinear sRGB values suitable for display on a computer\r\n% monitor.\r\nRGB = lin2rgb(RGB_lin);\r\n\r\nplotrgb(lambda(:),RGB)\r\n\r\n%%\r\ndrawColorbar(RGB)\r\n\r\n%%\r\n% Next time, I'll talk about how to draw the spectral color scale\r\n% underneath the plot.\r\na=5;\r\n\r\n%% Utility functions\r\nfunction drawColorbar(rgb_colors)\r\nf = figure;\r\nf.Position(4) = f.Position(4) \/ 5;\r\ncolorSwatches(rgb_colors,0)\r\ndaspect([40 1 1])\r\naxis off\r\nend\r\n\r\nfunction plotrgb(x,rgb_colors)\r\n% Pick the colors we want to use from the normal line color order.\r\nc = lines(7);\r\nblue = c(1,:);\r\nred = c(2,:);\r\ngreen = c(5,:);\r\nclf\r\nplot(x,rgb_colors(:,1),'Color',red);\r\nhold on\r\nplot(x,rgb_colors(:,2),'Color',green);\r\nplot(x,rgb_colors(:,3),'Color',blue)\r\nhold off\r\ngrid on\r\naxis tight\r\nyl = ylim;\r\nylim([min(yl(1)-0.05,-0.05) max(yl(2)+0.05,1.05)])\r\n\r\nlegend(\"R\",\"G\",\"B\")\r\nxlabel(\"wavelength (nm)\")\r\n\r\nend\r\n\r\nfunction shadeGamutRegion\r\nxl = xlim;\r\nxx = [xl(1) xl(2) xl(2) xl(1) xl(1)];\r\nyy = [0 0 1 1 0];\r\np = patch(xx,yy,[0.5 0.5 0.5],\"FaceAlpha\",0.1,...\r\n    \"EdgeAlpha\",0.1,\"HandleVisibility\",\"off\");\r\nend\r\n\r\n##### SOURCE END ##### ed0fb3806b5044c18980507c638fce3f\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/spectrum_colors_explanation_03.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><!--introduction--><p>It was a while ago now, but on <a href=\"https:\/\/blogs.mathworks.com\/steve\/files\/illuminant-d65.png\">April 27<\/a> I started explaining how I made this plot, which is from <a href=\"http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\"><b>DIPUM3E<\/b><\/a> (<i>Digital Image Processing Using MATLAB<\/i>, 3rd ed.):... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2020\/07\/20\/making-color-spectrum-plots-part-2\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":4133,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[50,278,248,214,725,70,1183,90,723,92,1314,1308,122,380,210,68,52,94,360,1312,298],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/4115"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/users\/42"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/comments?post=4115"}],"version-history":[{"count":2,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/4115\/revisions"}],"predecessor-version":[{"id":4119,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/4115\/revisions\/4119"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/4133"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=4115"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=4115"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=4115"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}