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 rgb2hsl_new(r,g,b) {
  let a=Math.max(r,g,b), n=a-Math.min(r,g,b), f = (1-Math.abs(a+a-n-1)); 
  let h= n && ((a==r) ? (g-b)/n : ((a==g) ? 2+(b-r)/n : 4+(r-g)/n)); 
  return [60*(h<0?h+6:h), f ? n/f : 0, (a+a-n)/2];
}

function rgb2hsl_wiki(r,g,b) {
  let a = Math.max(r,g,b); 
  let i = Math.min(r,g,b);
  let n = a-i;
  let l = (a+i)/2  
  let f= (1-Math.abs(a+i-1));
  let s = f ? n/f : 0;
  let h= 0;
  if(n) {
  	if(a==r) h=60*( 0+(g-b)/n );
  	if(a==g) h=60*( 2+(b-r)/n );
  	if(a==b) h=60*( 4+(r-g)/n );
  }
  
  return [(h<0?h+360:h)%360,s,l];
}


// ----------------------
// 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 = rgb2hsl_wiki(...c); 
        	n = rgb2hsl_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);
    
}

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
  chartH(0,0,'G',which,1) // clean
  chartH(0,0,'B',which,1) // clean
  let rgb2hsv = which == 'Wiki' ? rgb2hsl_wiki : rgb2hsl_new;
  for(let i=0; i<256; i++) {
    let c= i/255;
    chartH(c, rgb2hsv(c, color[1], color[2])[0], 'R',which )
    chartH(c, rgb2hsv(color[0], c, color[2])[0], 'G',which )
    chartH(c, rgb2hsv(color[0], color[1], c)[0],...