HSV to RGB

by Lamik

HTML

<div>Charts showing the hue (Y axis) as function of (X axis) r (red line), g (green line), b (blue line). There is also button for error comparison with wiki version.</div>
<div>R <input type="range" min="0" max="255" value="0" oninput="setC(0,event)"></div>
<div>G <input type="range" min="0" max="255" value="0" oninput="setC(1,event)"></div>
<div>B <input type="range" min="0" max="255" value="0" oninput="setC(2,event)"></div>

<div>Wiki-Opt max err <button onclick="calcErr()" >Calc (~1min, progress in console)</button> <span id="error"></span></div>

<div id="rgb"></div>
<div id="hsvNew"></div>
<div id="hsvWiki"></div>

<div id="labelOpt">New</div>

<svg viewBox="0 0 100 10" class="chart">    
  <text x="0" y="10" class="axis">0</text>
  <text x="0" y="2" class="axis">360 H</text>
  <text x="98" y="10" class="axis">1</text>
  <polyline id="chartNewRH" fill="none" stroke="#990000" stroke-width="0.2" points=""/>
  <polyline id="chartNewGH" fill="none" stroke="#009900" stroke-width="0.2" points=""/>
  <polyline id="chartNewBH" fill="none" stroke="#000099" stroke-width="0.2" points=""/>
</svg>

<div id="labelOpt">Wiki</div>

<svg viewBox="0 0 100 10" class="chart">    
  <text x="0" y="10" class="axis">0</text>
  <text x="0" y="2" class="axis">360 H</text>
  <text x="98" y="10" class="axis">1</text>
  <polyline id="chartWikiRH" fill="none" stroke="#990000" stroke-width="0.2" points=""/>
  <polyline id="chartWikiGH" fill="none" stroke="#009900" stroke-width="0.2" points=""/>
  <polyline id="chartWikiBH" fill="none" stroke="#000099" stroke-width="0.2" points=""/>
</svg>

<div class="test">  
  <div id="colA" class="col"></div>
  <div id="colB" class="col"></div>
</div>

CSS

.col { margin: 5px; width: 50px; height: 50px; }
#btn { display: block; margin: 10px; }
.test { display: flex; height: 400px}
.color { width: 50px; height: 1px; }
.chart { border: 1px solid black; }
.axis { font: 2px sans-serif;}
#labelOpt { margin-top: 20px }

JavaScript

function rgb2hsv_new(r,g,b) {
  let v = Math.max(r,g,b); 
  let 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 rgb2hsv_wiki(r,g,b) {  
  let v = Math.max(r,g,b); 
  let i = Math.min(r,g,b);  
  let n = v-i;
  let s=n/v;
  let h= 0;
  if(n) {
  	if(v==r) h=60*( 0+(g-b)/n );
  	if(v==g) h=60*( 2+(b-r)/n );
  	if(v==b) h=60*( 4+(r-g)/n );
  } else h=0;
  
  return [(h+360)%360,s,v];
}


// ----------------------
// TEST code
// ----------------------


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

$ = x=>document.querySelector(x);

function calcMaxError() {
    
    lastErr = {e:0};
   
    for(let i=0; i<256; i++) {
      console.log(`calculation...${(100*i/255)|0}%`)      
    	for(let j=0; j<256; j++) {         
      	for(let k=0; k<256; k++) {
          let c = [i/255, j/255, k/255];
          w = rgb2hsv_wiki(...c); 
        	n = rgb2hsv_new(...c); 
          e = Math.max( Math.abs(n[0]-w[0]), Math.abs(n[1]-w[1]), Math.abs(n[2]-w[2]));
          if(lastErr.e<e) lastErr = {e, wiki:w, new:n, c, i ,j, k}
        }
      }
    }
    $('#error').innerHTML = JSON.stringify(lastErr);
    
/*     
    for(let h=0; h<360; h++) {        
        w = rgb2hsv_wiki(...color); 
        n = rgb2hsv_new(...color); 
        e = Math.max( Math.abs(n[0]-w[0]), Math.abs(n[1]-w[1]), Math.abs(n[2]-w[2]));
        err.push({ h, e, w, n });
    }
    
    err.sort((a,b)=> b.e-a.e);
    $('#error').innerHTML = `err=${err[0].e} for hue=${err[0].h}&deg 
    <br>
    rgb=${color} <br>
    w=${w} <br>
    n=${n}
    ` */
}

function chartH(x,y,c,which,r) {    
    let a = $(`#chart${which}${c}H`).getAttribute('points') +` ${x*100}, ${(10-y/36)}`;    
    $(`#chart${which}${c}H`).setAttribute('points',r? "":a);    
}


function draw(which) {
  chartH(0,0,'R',which,1) // clean
 ...