Search code examples
javascriptcolorsrgbhsvcolor-theory

math behind hsv to rgb conversion of colors


i was going through the algorithm for converting hsv to rgb color space & found this on wikipdia. https://en.wikipedia.org/wiki/HSL_and_HSV. I found on eimplementation for this algorithm here.https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c.

but as far as i can see how are two related and what is the math behind arriving these calculations.

here is the code

/**
 * Converts an HSV color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
 * Assumes h, s, and v are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 255].
 *
 * @param   Number  h       The hue
 * @param   Number  s       The saturation
 * @param   Number  v       The value
 * @return  Array           The RGB representation
 */
function hsvToRgb(h, s, v){
    var r, g, b;

    var i = Math.floor(h * 6);
    var f = h * 6 - i;
    var p = v * (1 - s);
    var q = v * (1 - f * s);
    var t = v * (1 - (1 - f) * s);

    switch(i % 6){
        case 0: r = v, g = t, b = p; break;
        case 1: r = q, g = v, b = p; break;
        case 2: r = p, g = v, b = t; break;
        case 3: r = p, g = q, b = v; break;
        case 4: r = t, g = p, b = v; break;
        case 5: r = v, g = p, b = q; break;
    }

    return [r * 255, g * 255, b * 255];
}.

i want to understand the logic behind it & the math used to derive at these specific values used in conversion algorithm.


Solution

  • The easy way it is to design it by yourself (and then check the differences), and it is easier to do the inverse way: from RGB to HSV. Your formula should be just the mathematically inverse operation (do it with floating point, or you will lose information). In following explaination I'll use values from 0 to 1 (so scale by dividing with 255, if you are using 8-bit RGB).

    Value is just defined as the maximum value of RGB (just to keep thing simpler), if you need brightness, you should weight the 3 channels and sum them. With this definition (of value), we can have the brightest Blue, Red, and Green with same value, which it is handy for colour picker. OTOH we know that a complete blue screen (full with [0,0,255]) is darker then a completely green screen [0,255,0].

    Hue indicates the pure colours (if fully saturated), the colour on a colour wheel. These are done by mixing just two components of R,G,B (the third component should be 0). Why this? Take a typical CIE xy figure (e.g. on https://en.wikipedia.org/wiki/SRGB). Pure hues are the spectral colours, so with just one wavelength (the outer colours) plus the line of purples (the lower line), so mixing deeper violet with deeper red. With RGB we can display just the content of triangle (+ the colours on the missing z (or Y) axis). So the most similar and purer colours are the colour on the edge of the triangle between the R, G, B points. And points on a triangle have just one or two components. [CIE xy figures is nice because mixing two colours should be in the line between the two colours].

    One of the property of good colour wheel (for mixing, not perceptual) is:by mixing one colour with the opposite (on the wheel), one get grey. So opposite colours are opposite also on the wheel. So we have 6 colours: red [255,0,0], yellow [255,255,0], green [0,255,0], cyan [0,255,255], blue [0,0,255], and magenta [255,0,255]. These are the six cases, 60 degrees apart. So if you have [255, 20, 0] you are 20/255 from red to yellow (where 255/255 is 60 degrees). If you write down all cases, you see that you can eventually simplify calculations [which is usually done in common libraries]

    Saturation: Full saturation is like hue (so one component is zero) and full unsaturated colours are greys (or black or white) so with all components with the same value. So the differences of maximum component with minimum component is a saturation.

    Why not taking the minimum, as in some formula? This is also a good definition (and maybe more precise), and used on some places. But a darken colour is seems also unsaturated (so "mixing" black or white should have similar bahaviour, so the differences instead of minimum). Ihis manner (difference), saturation is independent of value, and it is more intuitive for a colour picker. The saturation is than divided by maximum component value. Also this is just to have a 0 to 1 number for every case.

    Exceptions: then there are some expection (you will see if you implement the calculations, e.g. if all component values are equal, the hue is defined as zero (it do no matter: full unsaturated colours do not have hue). Saturation of black cannot be calculated (zero divided by zero), but it is unsaturated colour, so we set it as 0.

    If you derive the inverse, you see your formula.

    Note: these calculation are usually done for RGB (so they are not real hue or saturation, or lightness), and for colour picker, so standardized value from 0 to 1 (or 100, or 360 for hue). In reality minimum saturation (so maximum colorfulness) depends in hue. Saturation should depend on brightness, etc. So a more physical color picker should not be a circle (cylinder, double cone), but a figure with more complex form. Which is not efficient for storage (lost of information e.g. by putting in integer: not all combination are valid), and often less intuitive.