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!