Friday, January 19, 2015
Generating RGBY (RGBA) Values from Hue
Theoretically, you can generate any perceptible color of light by mixing the three primary colors: red, green and blue. However some hues may not as be perceptually satisfying, for example the yellow hue formed by mixing red and green light. Adding extra colors of light to your mix can improve the gamut, that is the range of colors that can be rendered.
This image shows the gamut of colors available from a RGB mixture (the central triangle) and the quadrilateral shows the extra hues available when a amber channel (in this case a 590 nm amber) is added to the gamut. It's not a huge increase but it should appreciably improve both the yellow spectral hues as well as the color rendering index (CRI).
I recently obtained some color LED theater lights that have a set of amber LEDs to go with the conventional red, green, and blue. While it's straightforward to go from a HSV colorspace to RGB, it's less so to go to a four-channel RGBY space (and here I call the amber/yellow channel "Y" to distinguish it from the more common "A" that denotes the alpha or transparency channel).
Here's how a conventional
hsv_to_rgb() method generates R, G, and B
values from hue by piecewise-linear mapping. Every output color gets
1/3 of the hue range plus some overlap.
Now it's straightforward to just add another output channel with a similarly symmetric mapping such that output colors get 1/4 of the hue range. However that's not quite optimal, because the perceptual distance between red and amber is much smaller than the distance between the other primaries. Geometrically, this would be giving the right leg of the gamut quadrilateral the same weight as the others when it is actually and perceptually much shorter.
So a straightforward addition to this is to "squish" the red and amber outputs into the hue region that red alone used to take. Geometrically, the two left sides of the quadrilateral are mapped to the side of the triangle. This is still not perfect (it gives red and amber equal weight when amber should get a little more length). That's a little trickier mathematically but it's still easy to do, see the full iPython notebook for code and details
So I implemented this in a custom DMX controller for my theater lights (more on that in a lter post), with all methods available so I could compare the difference. Though it's impossible to capture photographically, the yellows and oranges were much more pleasingly saturated with the added amber channel, and the "squished" hue mapping was perceptually smoother. Here's the code on Github, and here's a screenshot of the controls implemented in wxPython:
As to next steps from here, let me put up one last image of the Planckian locus with the RGBY gamut quadrilateral superimposed. The color of blackbody radiation like incandescent lamps falls on the Planckian locus -- perhaps you can guess where I am going with this RGBY color work!