Javascript точно конвертирует цвет HSB / HSV в RGB

Мне нужно точно преобразовать HSB в RGB, но я не знаю, как обойти проблему преобразования десятичных дробей в целые числа без округления. Это текущая функция из библиотеки colorpicker:

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) };

Как видите, неточность в этой функции связана с Math.round


person that_guy    schedule 21.06.2013    source источник
comment
Проверьте это axonflux.com/handy -rgb-to-hsl-and-rgb-to-hsv-color-model-c   -  person Parthik Gosar    schedule 21.06.2013
comment
Выполняйте целочисленное преобразование только в самом конце.   -  person Paul S.    schedule 21.06.2013
comment
Я не тестировал это, но то, что вы сказали, имеет большой смысл, @PaulS.   -  person Rodrigo    schedule 12.09.2015
comment
Это связанный получивший высокую оценку ответ, на который вы, возможно, тоже захотите взглянуть. В нем обсуждаются преобразования цвета и создание градиентов, которые кажутся точными для глаз.   -  person kashiraja    schedule 06.04.2017


Ответы (6)


Из Парфика Госара hsl-and-rgb-to-hsv-color-model-c "rel =" noreferrer "> ссылка в этом комментарий с небольшими изменениями, позволяющими вводить каждое значение отдельно или все сразу как объект

/* 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)
    };
}

Этот код ожидает 0 <= h, s, v <= 1, если вы используете градусы или радианы, не забудьте разделить их.

Возвращенные 0 <= r, g, b <= 255 округляются до ближайшего целого числа. Если вы не хотите, чтобы такое поведение было, удалите Math.rounds из возвращаемого объекта.


И наоборот (с меньшим разделением)

/* 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
    };
}

Этот код выведет 0 <= h, s, v <= 1, но на этот раз принимает любое 0 <= r, g, b <= 255 (не обязательно целое число)


Для полноты картины

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
    };
}

Все эти значения должны находиться в диапазоне от 0 до 1. Для HSL<->RGB используйте HSV.

person Paul S.    schedule 21.06.2013
comment
Это то, что я ищу! Пришлось разделить h / 360 и s, v / 100. Работает отлично. - person that_guy; 22.06.2013
comment
Вы уверены, что он должен быть напольным, а не закругленным? - person mpen; 29.01.2015
comment
@Mark зависит от того, где вы будете лучше всего накапливать свои ошибки. Если v равно 1, вы должны получить 255 при настиле пола, так что это не значит, что вы не можете получить белые - person Paul S.; 29.01.2015
comment
@PaulS. Да, но вы получаете 0 от 0 до .9999, тогда как вы можете получить только 255 с идеальной 1. Округление разделит это поровну и обеспечит более точное совпадение для всех значений между ними, нет? - person mpen; 29.01.2015
comment
@Mark, теперь я тоже написал обратный метод, я вижу, что вы правы - округление имеет больше смысла, поэтому, если вы войдете и вернетесь, вы должны получить то, что вы вставили (при условии int rgb) - person Paul S.; 02.07.2015
comment
{v: _v} не {v:_v;}, которые режут глаза - person caub; 14.09.2016
comment
Для моего Infinity Ergodox мне нужно выполнить преобразование, но все значения, включая HSI, должны находиться в диапазоне от 0 до 255. Могу я спросить вас, что мне нужно изменить, чтобы он работал таким образом? - person Olivier Pons; 21.09.2017
comment
@OlivierPons, чтобы преобразовать диапазон между 0..1 и 0..n, используйте простое умножение или деление на n. Поскольку HSI также использует середину, я не думаю, что это так просто преобразовать без использования RGB; здесь красивое изображение, описывающее их отношения математически - person Paul S.; 22.09.2017
comment
Совершенно не имеет отношения к JavaScript, но я перенес его на PHP. Мне по какой-то причине не удалось найти в PHP версию, которая работала бы правильно. Вот он, если кому нужно: gist.github.com/kkirby/10c1bd47ac10f90b521eb1238f47b94 - person Kyle; 01.04.2020

Коротко, но точно

Попробуйте это (подробнее: rgb2hsv, hsl2rgb, rgb2hsl и sl22sv)

// input: h in [0,360] and s,v in [0,1] - output: r,g,b in [0,1]
function hsv2rgb(h,s,v) 
{                              
  let f= (n,k=(n+h/60)%6) => v - v*s*Math.max( Math.min(k,4-k,1), 0);     
  return [f(5),f(3),f(1)];       
}   

// oneliner version
let hsv2rgb=(h,s,v,f=(n,k=(n+h/60)%6)=>v-v*s*Math.max(Math.min(k,4-k,1),0))=>[f(5),f(3),f(1)];

console.log(`hsv: (340,0.3,0.9) -> rgb: (${hsv2rgb(340,0.3,0.9)})`)


// ---------------
// UX
// ---------------

rgb= [0,0,0];
hs= [0,0,0];

function rgb2hsv(r,g,b) {
  let v=Math.max(r,g,b), n=v-Math.min(r,g,b);
  let h= n && ((v==r) ? (g-b)/n : ((v==g) ? 2+(b-r)/n : 4+(r-g)/n)); 
  return [60*(h<0?h+6:h), v&&n/v, v];
}

function changeRGB(i,e) {
  rgb[i]=e.target.value/255;
  hs = rgb2hsv(...rgb);
  refresh();
}

function changeHS(i,e) {
  hs[i]=e.target.value/(i?255:1);
  rgb= hsv2rgb(...hs);
  refresh();
}

function refresh() {
  rr = rgb.map(x=>x*255|0).join(', ')
  tr = `RGB: ${rr}`
  th = `HSV: ${hs.map((x,i)=>i? (x*100).toFixed(2)+'%':x|0).join(', ')}`
  box.style.backgroundColor=`rgb(${rr})`;  
  infoRGB.innerHTML=`${tr}`;  
  infoHS.innerHTML =`${th}`;  
  
  r.value=rgb[0]*255;
  g.value=rgb[1]*255;
  b.value=rgb[2]*255;
  
  h.value=hs[0];
  s.value=hs[1]*255;
  v.value=hs[2]*255;  
}

refresh();
body { display: flex; }
.box { width: 50px; height: 50px; margin: 20px; }
<div>
  <input id="r" type="range" min="0" max="255" oninput="changeRGB(0,event)">R<br>
  <input id="g" type="range" min="0" max="255" oninput="changeRGB(1,event)">G<br>
  <input id="b" type="range" min="0" max="255" oninput="changeRGB(2,event)">B<br>
  <pre id="infoRGB"></pre>
</div> 

<div id="box" class="box hsl"></div>

<div>
  <input id="h" type="range" min="0" max="360" oninput="changeHS(0,event)">H<br>
  <input id="s" type="range" min="0" max="255" oninput="changeHS(1,event)">S<br>
  <input id="v" type="range" min="0" max="255" oninput="changeHS(2,event)">V<br>
  <pre id="infoHS"></pre><br>
</div>

Вот формулы, которые я обнаружил и точно описал в wiki + error анализ

введите описание изображения здесь

person Kamil Kiełczewski    schedule 03.01.2019
comment
Получил отрицательные значения! - person MR_BD; 01.01.2021
comment
@MR_BD, какие именно значения h, s, v вы получаете отрицательные значения? - person Kamil Kiełczewski; 01.01.2021
comment
Теперь я понял, что мне нужно масштабировать его от 0 до 1. Я использовал 80 для 80%. - person MR_BD; 01.01.2021

Учитывая растущую популярность npm, я думаю, стоит упомянуть пакет, содержащий все эти функции. через простой API:

npm install colorsys

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

Для браузера: <script src="http://netbeast.github.io/colorsys/browser.js"></script>

colorsys.rgb_to_hex(h, s, v)
// #hexcolor
person jsdario    schedule 12.02.2016

Однажды я написал эту функцию:

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)
    };
}

Он ожидает 0 ‹= H‹ = 360, 0 ‹= S‹ = 1 и 0 ‹= V‹ = 1 и возвращает объект, содержащий R, G и B (целые значения от 0 до 255). Я использовал это изображение для создания кода.

person sigalor    schedule 18.07.2015

В функции HSVtoHSL Пола S есть ошибка: когда вход v равен 0, мы получаем проблему деления на ноль, а результат s становится NaN. Вот исправление:

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. Я бы добавил это как комментарий к сообщению Пола С., но я новичок, и система сказала мне, что у меня недостаточно репутации.

person benlambert    schedule 15.09.2016

Вот алгоритм в unityscript, вам придется переписать функции float, mathf.floor, и на выходе будет вектор3 из 3 чисел с плавающей запятой.

ИЗМЕНИТЬ Это был первый ответ на этой странице, и казалось целесообразным оказать такую ​​помощь, когда не было других ответов, с той небольшой оговоркой, что вы должны преобразовать его в почти идентичную версию на 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); 
}
person DeltaEnfieldWaid    schedule 18.06.2014