Published October 1, 2018 by Scott Allen Burns, last updated Oct 2, 2018
10/2/18 Added section of real object colors and XYZ color space.
In a previous presentation on generating reflectance curves from sRGB triplets, I presented several algorithms for creating a reasonably realistic spectral reflectance curve associated with a given sRGB color. This publication is a short note on applying the methods to a more recent RGB implementation called Recommendation ITU-R BT.2020-2 (10/2015), or Rec. 2020 for short.
Generating a New Matrix
The colorimetric specification of this RGB system is:
The main task in applying the reflectance generating algorithms to Rec. 2020 is to create a new matrix (called here) that relates reflectance, to linear Rec. 2020 rgb. Once this is done, just substitute for in any of the previous algorithms to make them specific to Rec. 2020. Recall that is defined as
where is a 3×36 matrix of color matching functions (CMFs) that relate a 36×1 reflectance vector, , to tristimulus values. is a 36×1 illuminant vector, which is Standard Illuminant D65 in this case. The expression diag() refers to a square 36×36 matrix with on the main diagonal and zeros elsewhere. is a weighting factor that is calculated as the dot (scalar) product of the second row of and . The matrix here is the tricky part. It relates to linear and must be custom built for Rec. 2020.
Bruce Lindbloom has a handy page that describes the creation of M. The computation only requires the chromaticity coordinates of the RGB primaries and the tristimulus values of the reference white. For D65, these are = (0.95047, 1.00000, 1.08883).
I made one minor adjustment to the Rec. 2020 specs to compensate for the 10 nm intervals in our calculations. The R/G/B chromaticity coordinates shown in Table 3 above correspond to single wavelength primaries at 630 nm, 532 nm, and 467 nm, respectively. Since the last two wavelengths don’t fall on the 10 nm intervals, I modified them just slightly. The G primary is changed to be the combination of 80% 530 nm light and 20% 540 nm light. The B primary is changed to be 30% 460 nm light and 70% 470 nm light. This is shown graphically to the right (click to enlarge).
The chromaticity coordinates of the slightly modified primaries come out to be (, ) = (0.7079, 0.2920), (, ) = (0.1718, 0.7941), and (, ) = (0.1312, 0.0478). This change greatly improves the stability of the optimization process (by avoiding slight constraint infeasibility very near the spectral envelope) and changes the values by only 0.00074 at most.
Plugging these values into the equations on Bruce Lindbloom’s page yields an matrix of:
We now have all the ingredients to build our matrix:
which is available here.
Here are some results from applying a general-purpose optimization code (Matlab’s fmincon) to the nonlinear program (ILSS), using instead of the sRGB version of ,
I tried to use a highly saturated rgb, like (1,0,0), but the method failed (the optimizer reported it lost constraint feasibility). I used progressively smaller values for r until I found something that worked, rgb = (0.079, 0, 0). It ended up being a single wavelength spike at the Rec. 2020 r primary (630 nm). Even though the optimizer was trying to find the reflectance curve with least slope squared, it had no recourse but to end up with a very non-smooth solution. This issue is addressed in the next section.
Rec. 2020 and “Real” Reflectances
When I originally developed the methods for converting sRGB values to reflectances, I was pleased to find that they worked for all values of sRGB. Furthermore, the two methods that guarantee reflectance curves in the range 0 to 1 produced a valid answer for all sRGB inputs. This will not be the case with Rec. 2020. Because the primaries are single wavelength lights, there are values of linear Rec. 2020 rgb triplets that have no corresponding reflectance curves in the range 0 to 1.
Consider a reflectance curve with all zeros except at 630 nm, which is given a value of 1. This is the red primary in this color space. If we pre-multiply this reflectance by , we will get the Rec. 2020 rgb triplet corresponding to the reflectance. It comes out to be rgb = (0.0798, 0, 0). This reflectance represents a very dim, but extremely saturated red color. If we try to find a reflectance curve with a larger red rgb component, the only way that is possible is for the reflectance to exceed 1 in some places. This will manifest itself in the optimization methods as lost feasibility in the case of general-purpose optimization codes, or as bad conditioning (singular matrices) in the case of the tightly implemented algorithms like ILLSS, etc.
I was curious to find out just how much of the Rec. 2020 color space can be represented by reflectance curves with 0-1 range. I vaguely recall reading about these types of limits in the literature (was it called the “MacAdam limit”?), but I preferred to see if I could find them myself. So I wrote a program that generated a long series of reflectance curves that contained only 1s and 0s, having one or two contiguous blocks of 1’s and 0s elsewhere. All of these reflectance curves were then premultiplied by to get their corresponding rgb values, which were then plotted in Rec. 2020 color space. The rgb values formed the boundary of a region in Rec. 2020 color space (roll mouse over to animate):
Here are some views from specific angles:
The conclusion here is that if Rec. 2020 is used with any or the optimization-based methods, an additional check to make sure the rgb values are within this “real” color region should be performed to make the implementation robust. How to do that is not obvious to me right now. The only thing I can think of is to characterize the region as a convex hull (there are tools in Matlab to do this) and then check that the desired rgb falls within the convex hull. Quite expensive computationally!
One final observation relates to the spiked reflectance curve for rgb = (0.079, 0, 0), above. If a tiny bit of another rgb component is added, i.e., rgb = (0.079, 0.079, 0), the nature of the curve changes dramatically:
Apparently, the additional green component pulls the color far enough away from the spectral envelope to allow the optimizer to find the broad, smooth spectrum reflectance curve it is seeking.
While on the subject of other color spaces, it is a trivial matter to apply the minimum slope-squared methods to the space of tristimulus values, XYZ. The matrix in this case degenerates to the identity matrix. The XYZ space has even more non-real object colors since the primaries fall outside the spectral envelope and represent “impossible” colors for human color vision. Here are plots of the region of “real” object colors (capable of having a 0-1 reflectance representation):
Thanks to Mark (user kram1032), Chris Cook (user smilebags) and Troy Sobotka (troy_s) on a Blender discussion site for the interesting discussions that prompted me to look into this.
Rec. 2020 RGB to Spectrum Conversion for Reflectances by Scott Allen Burns is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.