javascriptconvertersrgbhsvhsb

Javascript convert HSB/HSV color to RGB accurately


I need to accurately convert HSB to RGB but I am not sure how to get around the problem of turning decimals into whole numbers without rounding. This is the current function I have out of a colorpicker library:

HSBToRGB = function (hsb) {

    var rgb = { };
    var h = Math.round(hsb.h);
    var s = Math.round(hsb.s * 255 / 100);
    var v = Math.round(hsb.b * 255 / 100);

        if (s == 0) {

        rgb.r = rgb.g = rgb.b = v;
        } else {
        var t1 = v;
        var t2 = (255 - s) * v / 255;
        var t3 = (t1 - t2) * (h % 60) / 60;

            if (h == 360) h = 0;

                if (h < 60) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3 }
                else if (h < 120) { rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3 }
                else if (h < 180) { rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3 }
                else if (h < 240) { rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3 }
                else if (h < 300) { rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3 }
                else if (h < 360) { rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3 }
                else { rgb.r = 0; rgb.g = 0; rgb.b = 0 }
        }

    return { r: Math.round(rgb.r), g: Math.round(rgb.g), b: Math.round(rgb.b) };

As you can see the inaccuracy in this function comes from the Math.round


Solution

  • From Parthik Gosar's link in this comment with slight modification to let you enter each value independently or all at once as an object

    /* accepts parameters
     * h  Object = {h:x, s:y, v:z}
     * OR 
     * h, s, v
    */
    function HSVtoRGB(h, s, v) {
        var r, g, b, i, f, p, q, t;
        if (arguments.length === 1) {
            s = h.s, v = h.v, h = h.h;
        }
        i = Math.floor(h * 6);
        f = h * 6 - i;
        p = v * (1 - s);
        q = v * (1 - f * s);
        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: Math.round(r * 255),
            g: Math.round(g * 255),
            b: Math.round(b * 255)
        };
    }
    

    This code expects 0 <= h, s, v <= 1, if you're using degrees or radians, remember to divide them out.

    The returned 0 <= r, g, b <= 255 are rounded to the nearest Integer. If you don't want this behaviour remove the Math.rounds from the returned object.


    And the reverse (with less division)

    /* accepts parameters
     * r  Object = {r:x, g:y, b:z}
     * OR 
     * r, g, b
    */
    function RGBtoHSV(r, g, b) {
        if (arguments.length === 1) {
            g = r.g, b = r.b, r = r.r;
        }
        var max = Math.max(r, g, b), min = Math.min(r, g, b),
            d = max - min,
            h,
            s = (max === 0 ? 0 : d / max),
            v = max / 255;
    
        switch (max) {
            case min: h = 0; break;
            case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break;
            case g: h = (b - r) + d * 2; h /= 6 * d; break;
            case b: h = (r - g) + d * 4; h /= 6 * d; break;
        }
    
        return {
            h: h,
            s: s,
            v: v
        };
    }
    

    This code will output 0 <= h, s, v <= 1, but this time takes any 0 <= r, g, b <= 255 (does not need to be an integer)


    For completeness,

    function HSVtoHSL(h, s, v) {
        if (arguments.length === 1) {
            s = h.s, v = h.v, h = h.h;
        }
        var _h = h,
            _s = s * v,
            _l = (2 - s) * v;
        _s /= (_l <= 1) ? _l : 2 - _l;
        _l /= 2;
    
        return {
            h: _h,
            s: _s,
            l: _l
        };
    }
    
    function HSLtoHSV(h, s, l) {
        if (arguments.length === 1) {
            s = h.s, l = h.l, h = h.h;
        }
        var _h = h,
            _s,
            _v;
    
        l *= 2;
        s *= (l <= 1) ? l : 2 - l;
        _v = (l + s) / 2;
        _s = (2 * s) / (l + s);
    
        return {
            h: _h,
            s: _s,
            v: _v
        };
    }
    

    All of these values should be in the range 0 to 1. For HSL<->RGB go via HSV.