Wednesday, July 27, 2022
How to convert RGBA Amber to HSV
Some time ago, I obtained some LED par lamps that had amber LEDs in addition to the usual red, green and blue. This makes for generally warmer oranges and yellows as it increases the color gamut available. To control the colors in a natural way, I wrote a little routine to squeeze the orange hues into the usual color wheel (hue, saturation, value) representation. More details can be found in this blog post: /2015/Generating-RGBY-from-Hue/.
Fast-forward several years when someone asked me if the reverse transformation is possible. Turns out it is, but it's definitely more complicated and not at all perfect. (This is to be expected, as we are converting from a four-dimensional (R, G, B, A) space to the three dimensions of H, S, V so naturally the solution is only an approximation.)
To do this, I used the perceptually-based CIE
xyY
to model what color a mix of RGBA primaries will yield. Each primary
light source is represented as a point in xy
space, and they can be
interpolated to find the xy
coordinates of the resulting mixture.
Once this is determined, the xy
coordinates can be converted to HSV.
Here's an example of how that interpolation works: I've generated a ring of colors of different hues and similar saturation by appropriately mixing R, G, and B primaries. Mixing in a successively brighter amber LED shifts the colors towards the amber primary at 590nm.
Here's a library to do the color space conversions on Github: github.com/headrotor/colorxform/ Details on the various conversions follow.
The HSV_to_RGBA()
and HSV_to_RGBA_CIE()
methods divide the hue
range into four sections corresponding to red, amber, green, and blue,
and crossfades between them to arrive at an approximately correct
hue.
The first
HSV_to_RGBA()
is the straightforward approach where the amber values are squeezed in between the red and green (further described in this blog post:
/2015/Generating-RGBY-from-Hue/).
A more sophisticated approach HSV_to_RGBA_CIE()
gives colors that
are closer to equal angles in xy
space and should thus be a
perceptually smoother transition around the hue circle.
A benefit of the CIE approach is that a round-trip conversion HSV
→
RGBA
→ HSV
will result in hues that are much closer to the
original. The HSV_to_RGBA_CIE()
method also has an optional parameter
warp
that when set to true (the default) will prewarp the hue to
better match the RGBA_to_HSV_CIE()
results. warp
uses a 6th-order
polynomial fit to warp the input hue value; if round-trip hue matching
is of less importance then warp
mat be set to False to skip this
computation.
The following image shows
the locus in xy
space of hues equally spaced in 10 degree increments
given by HSV_to_RGBA()
on the left and HSV_to_RGBA_CIE()
on the
right. Note that the CIE version is more regularly spaced and so
should be more perceptually equal as well.
The RGBA_to_HSV_CIE()
function is somewhat more complex and is
naturally an approximation as we are converting from a
four-dimensional space to three. Each light source (R, G, B, and A) is
converted into into the CIE
xy
color coordinates, and interpolated to find the color of the resulting
mixture. Once this is calculated, the xy
coordinates can be
converted to HSV.
To convert from xy
coordinates to HSV, we need to determine the hue,
saturation, and value. The saturation can be obtained by the scaled
distance from the CIE white point (the ★ in the animation above) to
the xy
value, but in practice it is simpler to just use the minimum
of the RGBA values. The value, or brightness, is not specified in xy
space so the maximum of the RGBA values is used. Here is how this works for HSV-RGB, the extension to RGBA is straightforward.
To calculate the hue, we need to find the angular distance around the
hue circle. in xy
space, this is pretty well captured by the angle
around the white point. For example, given an arbitrary green color
(labeled g
here), the hue angle is the angle between vector g
(solid) and the red primary vector (dotted). So the hue angle for an
arbitrary color is calculated from the angle in xy
space by
reversing the direction (so increasing hue goes sequentially from red
to green to blue) and subtracting the red angle (so zero hue
corresponds to red).
Using the CIE conversion functions and warping reduce the round-trip
hue error to less than a few percent
(e. g. HSV_to_RGBA_CIE(warp=True)
→RGBA_to_HSV_CIE()
), as shown by
the green line in the following figure.
The warping is accomplished using a 6-degree polynomial fit to the difference between the input hue and output hue.
Note 1: color-space nomenclature is confusing!
Colorspace names are in uppercase except xyY
to disambiguate X
,
Y
from x
, y
from CIE xyY
space. Four-color space is RGBA
with A
for Amber. (Though Y
is often used for for amber/Yellow to
disambiguate from alpha channel "A", we use A
here to disambiguate
from Y
in xyY
and XYZ
color spaces.)