Javascript convert HSB/HSV color to RGB accurately

2019-01-03 15:57发布

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

5条回答
爷、活的狠高调
2楼-- · 2019-01-03 16:11

Here is the algorithm in unityscript, you'll have to rewrite the float, mathf.floor functions and the output is a vector3 of 3 floats.

EDIT This was the first answer on this page and it seemed worthwile to provide this much help when there weren't any other answers, with the small reservation that you have to convert it to a nearly identical version in JS.

function HSVToRGB(h : float, s : float, v : float) {
    h=h%1;
    s=s%1;
    v=v%1;

    var r : float;
    var g : float;
    var b : float;
    var i : float;
    var f : float;
    var p : float;
    var q : float;
    var t : float; 

    i = Mathf.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 Color(r,g,b); 
}
查看更多
来,给爷笑一个
3楼-- · 2019-01-03 16:18

Given the rising popularity of npm I think it is worth to mention a package containing all this functions through a simple API:

npm install colorsys

var colorsys = require('colorsys')
colorsys.rgb_to_hsv({ r: 255, g: 255, b: 255 })
// { h: 0 , s: 0 , v: 100 }

For the browser: <script src="http://netbeast.github.io/colorsys/browser.js"></script>

colorsys.rgb_to_hex(h, s, v)
// #hexcolor
查看更多
Root(大扎)
4楼-- · 2019-01-03 16:21

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.

查看更多
Rolldiameter
5楼-- · 2019-01-03 16:24

There's a bug in Paul S's HSVtoHSL function: when the v input is 0 we get a divide-by-zero problem and the s output becomes NaN. Here's a fix:

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 === 0 ? 1 : _l) : 2 - _l;
    _l /= 2;

    return {
        h: _h,
        s: _s,
        l: _l;
    };
}

P.S. I would have added this as a comment on Paul S.'s post, but I'm new and the system told me I don't have enough rep.

查看更多
Bombasti
6楼-- · 2019-01-03 16:26

I once wrote this function:

function mix(a, b, v)
{
    return (1-v)*a + v*b;
}

function HSVtoRGB(H, S, V)
{
    var V2 = V * (1 - S);
    var r  = ((H>=0 && H<=60) || (H>=300 && H<=360)) ? V : ((H>=120 && H<=240) ? V2 : ((H>=60 && H<=120) ? mix(V,V2,(H-60)/60) : ((H>=240 && H<=300) ? mix(V2,V,(H-240)/60) : 0)));
    var g  = (H>=60 && H<=180) ? V : ((H>=240 && H<=360) ? V2 : ((H>=0 && H<=60) ? mix(V2,V,H/60) : ((H>=180 && H<=240) ? mix(V,V2,(H-180)/60) : 0)));
    var b  = (H>=0 && H<=120) ? V2 : ((H>=180 && H<=300) ? V : ((H>=120 && H<=180) ? mix(V2,V,(H-120)/60) : ((H>=300 && H<=360) ? mix(V,V2,(H-300)/60) : 0)));

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

It expects 0<=H<=360, 0<=S<=1 and 0<=V<=1 and returns an object that contains R, G and B (integer values between 0 and 255). I used this image to create the code.

查看更多
登录 后发表回答