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
Ответы
Ответ 1
Из Партик Госар ссылка в этот комментарий с небольшими изменениями, чтобы вы могли вводить каждое значение независимо или все сразу как объект
/* 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.round
из возвращаемого объекта.
И наоборот (с меньшим делением)
/* 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.
Ответ 2
Учитывая растущую популярность npm, я думаю, что стоит упомянуть package, содержащий все эти функции с помощью простого 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
Ответ 3
Я однажды написал эту функцию:
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). Я использовал это изображение для создания кода.
Ответ 4
Попробуйте это (вики, анализ ошибок, больше: 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>
Ответ 5
Там появляется ошибка в функции Paul S HSVtoHSL
: когда вход 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. Я бы добавил это как комментарий к сообщению Пола С., но я новичок, и система сказала мне, что мне не хватает репутации.
Ответ 6
Вот алгоритм в unitscript, вам придется переписать функции float, mathf.floor, а выход - это вектор3 из 3 поплавков.
EDIT Это был первый ответ на этой странице, и было бы полезно предоставить эту большую помощь, когда не было никаких других ответов, с небольшой оговоркой, что вы должны преобразовать ее в почти идентичную версию в 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);
}