Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 761
that_guy Avatar asked Jun 21 '13 18:06

that_guy


People also ask

Can a color in HSV be converted to RGB?

RGB = hsv2rgb( HSV ) converts the hue, saturation, and value (HSV) values of an HSV image to red, green, and blue values of an RGB image. rgbmap = hsv2rgb( hsvmap ) converts an HSV colormap to an RGB colormap.

Is HSV and HSB the same?

HSB and HSV are the same, but HSL is different. HSB stands for Hue, Saturation, Brightness. HSV stands for Hue, Saturation, Value. HSL stands for Hue, Saturation, Light.

Which method is used to convert RGB color to HSV?

HSV = rgb2hsv( RGB ) converts the red, green, and blue values of an RGB image to hue, saturation, and value (HSV) values of an HSV image. hsvmap = rgb2hsv( rgbmap ) converts an RGB colormap to an HSV colormap.

How do you convert color space to RGB to HSV?

Convert RGB Image to HSV Image Convert the image to the HSV color space. HSV = rgb2hsv(RGB); Process the HSV image. This example increases the saturation of the image by multiplying the S channel by a scale factor.


2 Answers

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.

like image 159
Paul S. Avatar answered Sep 17 '22 14:09

Paul S.


Short but precise

Try this (more: rgb2hsv, hsl2rgb, rgb2hsl and 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>

Here are formulas which I discover and precisely describe in wiki + error analysis

enter image description here

like image 31
Kamil Kiełczewski Avatar answered Sep 19 '22 14:09

Kamil Kiełczewski