Drawing a color wheel... faster? Using the following code, I am able to neatly draw a color wheel, however, performance is barely acceptable on a high end machine. Now, I know some tricks I could employ to make it run far more smoothly, at the cost of a lot of flexibility, but I am especially interested in optimizing the code itself (thus keeping the flexibility). And of course in any generic feedback.



// Utility functions (ignore mostly)
function hsv2rgb(h, s, v) {  
    var c = v * s;  
    var h1 = h / 60;  
    var x = c * (1 - Math.abs((h1 % 2) - 1));  
    var m = v - c;  
    var rgb;  
    
    if (typeof h == 'undefined') rgb = [0, 0, 0];
    else if (h1 &lt; 1) rgb = [c, x, 0];
    else if (h1 &lt; 2) rgb = [x, c, 0];
    else if (h1 &lt; 3) rgb = [0, c, x];
    else if (h1 &lt; 4) rgb = [0, x, c];
    else if (h1 &lt; 5) rgb = [x, 0, c];
    else if (h1 &lt;= 6) rgb = [c, 0, x];
    
    return [255 * (rgb[0] + m), 255 * (rgb[1] + m), 255 * (rgb[2] + m)];
} 
function hsv2rgbString(h,s,v){
    var rgb = hsv2rgb(h,s,v);
    rgb = rgb.map(Math.round);
    return "rgb("+rgb[0]+","+rgb[1]+","+rgb[2]+")";
}

// Code
var canvas = document.getElementById("picker");
var context = canvas.getContext("2d");
var rangeEl = document.getElementById('range');
var x = canvas.width / 2;
var y = canvas.height / 2;
var borderWidth = 2;
var totalRadius = canvas.width / 2 - borderWidth;
var type = "hsv";

// To trigger anti aliasing, performance is poor even without it
context.moveTo(x, y);

var drawPicker = function(){
    context.clearRect (0, 0, 150, 150);
    context.lineWidth = 1;

    for(var i=0;i&lt;totalRadius;i++){
        var radius = totalRadius-i;
        
        if(type == "hsl"){
            var saturation = Math.floor(radius/totalRadius*100)/100;
            var lightness = rangeEl.value;
        }else if(type == "hsv"){
            var saturation = Math.floor(radius/totalRadius*100)/100;
            var value = rangeEl.value;
        }

        for(var angle=0; angle&lt;=360; angle+=1){
            var startAngle = (angle-1) * Math.PI/180;
            var endAngle = (angle+1) * Math.PI/180;
            context.beginPath();
            context.arc(x, y, radius, startAngle, endAngle, false);
            context.closePath();
            if(type == "hsl"){
                context.strokeStyle = 'hsl('+angle+', '+saturation*100+'%, '+lightness*100+'%)';
            }else if(type == "hsv"){
                context.strokeStyle = hsv2rgbString(angle,saturation,value);
            }
            context.stroke();
        }
    }
    
    // Draw the final black border
    context.strokeStyle = "rgb(0,0,0)";
    context.lineWidth = borderWidth;
    context.beginPath();
    var borderRadius = totalRadius+borderWidth/2;
    context.arc(x, y, borderRadius, 0, 2*Math.PI, false);
    context.closePath();
    context.stroke();
}

drawPicker();
rangeEl.addEventListener("change",drawPicker);
&lt;canvas id="picker" width="150" height="150"&gt;&lt;/canvas&gt;&lt;br/&gt;
&lt;input id="range" type="range" min="0" max="1" value="1" step="0.01"&gt;




I am asking for a code review, but if anybody knows what's causing the white lines (the Moiré pattern) appear at a low value (the slider) then I would be forever grateful. Or rather, I get why they appear, I just don't know how to solve it... beyond doubling the resolution and downscaling.
I like the idea of drawing it using arc segments, but it's probably not the most efficient. There's no doubt some extra loss in coverting to a CSS-style rgb(...) string, which then has to be parsed again. And, as you say, it causes the Moiré artefacts. Otherwise, the code looks pretty good.

While I won't guarantee it's always faster (and there are some browser support issues), you can try drawing the the color wheel pixel by pixel to an ImageData object, and pushing that to the canvas all at once. It seems faster, but I've also left out clipping the data to a wheel shape here.

You might also consider a square color field (rather than a wheel). That what this is of course, but the corners of it aren't too useful. Photoshop, for instance, defaults to a square field with saturation / lightness along the axes, and the slider determining the hue.

But Photoshop also has an RGB mode, where two of the components are used for the axes, and the last one is controlled by the slider. If you can construct a field that only requires you to update 1 byte per pixel, that'd probably be faster still.



var canvas = document.getElementById("picker");
var context = canvas.getContext("2d");
var rangeEl = document.getElementById('range');

function drawPicker() {
  // grab the current ImageData (or use createImageData)
  var bitmap = context.getImageData(0, 0, 150, 150);

  for (var y = 0; y &lt; 150; y++) {
    for (var x = 0; x &lt; 150; x++) {
      // offset for the 4 RGBA values in the data array
      var offset = 4 * ((y * 150) + x);

      var hue = 180 + Math.atan2(y - 75, x - 75) * (180 / Math.PI);
      var saturation = Math.sqrt(Math.pow(y - 75, 2) + Math.pow(x - 75, 2)) / 75;
      var value = rangeEl.value;

      saturation = Math.min(1, saturation);

      var hsv = hsv2rgb(hue, saturation, value);

      // fill RGBA values
      bitmap.data[offset + 0] = hsv[0];
      bitmap.data[offset + 1] = hsv[1];
      bitmap.data[offset + 2] = hsv[2];
      bitmap.data[offset + 3] = 255; // no transparency

    }
  }

  // update the canvas
  context.putImageData(bitmap, 0, 0);
}

// setup
drawPicker();
rangeEl.addEventListener("change", drawPicker);

// =========

function hsv2rgb(h, s, v) {
  var c = v * s;
  var h1 = h / 60;
  var x = c * (1 - Math.abs((h1 % 2) - 1));
  var m = v - c;
  var rgb;

  if (typeof h == 'undefined') rgb = [0, 0, 0];
  else if (h1 &lt; 1) rgb = [c, x, 0];
  else if (h1 &lt; 2) rgb = [x, c, 0];
  else if (h1 &lt; 3) rgb = [0, c, x];
  else if (h1 &lt; 4) rgb = [0, x, c];
  else if (h1 &lt; 5) rgb = [x, 0, c];
  else if (h1 &lt;= 6) rgb = [c, 0, x];

  var r = 255 * (rgb[0] + m);
  var g = 255 * (rgb[1] + m);
  var b = 255 * (rgb[2] + m);

  return [r, g, b];
}
&lt;canvas id="picker" width="150" height="150"&gt;&lt;/canvas&gt;
&lt;br&gt;
&lt;input id="range" type="range" min="0" max="1" value="1" step="0.01"&gt;




Alternative to that: Cheat! Make the CSS background of the canvas black, and fade the color wheel in front of it using the slider. That's one way to get away with just changing 1 byte per pixel, and letting the browser handle compositing.



var canvas = document.getElementById("picker");
var context = canvas.getContext("2d");
var rangeEl = document.getElementById('range');
var colorWheel = null;

// update the alpha bytes of the color wheel
function updatePicker() {
  var bitmap = context.getImageData(0, 0, 150, 150);
  for (var y = 0; y &lt; 150; y++) {
    for (var x = 0; x &lt; 150; x++) {
      var offset = ((y * 150) + x) * 4 + 3;
      colorWheel.data[offset] = rangeEl.value | 0;
    }
  }

  // update the canvas
  context.putImageData(colorWheel, 0, 0);
}

// setup
drawPicker();
rangeEl.addEventListener("change", updatePicker);

// =========

function hsv2rgb(h, s, v) {
  var c = v * s;
  var h1 = h / 60;
  var x = c * (1 - Math.abs((h1 % 2) - 1));
  var m = v - c;
  var rgb;

  if (typeof h == 'undefined') rgb = [0, 0, 0];
  else if (h1 &lt; 1) rgb = [c, x, 0];
  else if (h1 &lt; 2) rgb = [x, c, 0];
  else if (h1 &lt; 3) rgb = [0, c, x];
  else if (h1 &lt; 4) rgb = [0, x, c];
  else if (h1 &lt; 5) rgb = [x, 0, c];
  else if (h1 &lt;= 6) rgb = [c, 0, x];

  var r = 255 * (rgb[0] + m);
  var g = 255 * (rgb[1] + m);
  var b = 255 * (rgb[2] + m);

  return [r, g, b];
}

// create the initial color wheel
function drawPicker() {
  colorWheel = context.getImageData(0, 0, 150, 150);

  for (var y = 0; y &lt; 150; y++) {
    for (var x = 0; x &lt; 150; x++) {
      var offset = 4 * ((y * 150) + x);

      var hue = 180 + Math.atan2(y - 75, x - 75) * (180 / Math.PI);
      var saturation = Math.sqrt(Math.pow(y - 75, 2) + Math.pow(x - 75, 2)) / 75;

      saturation = Math.min(1, saturation);

      var hsv = hsv2rgb(hue, saturation, 1); // fixed value

      colorWheel.data[offset + 0] = hsv[0];
      colorWheel.data[offset + 1] = hsv[1];
      colorWheel.data[offset + 2] = hsv[2];
      colorWheel.data[offset + 3] = 255;
    }
  }
  context.putImageData(colorWheel, 0, 0);
}
#picker {
  background: black
}
&lt;canvas id="picker" width="150" height="150"&gt;&lt;/canvas&gt;
&lt;br&gt;
&lt;input id="range" type="range" min="0" max="255" value="255" step="1"&gt;




Or better yet: Cheat completely. Just put an image of a color wheel on top of a black box, and fade the image using the slider. No canvas, no pixel manipulation, none of that.



var wheel = document.getElementById("wheel"),
    slider = document.getElementById("slider");

slider.addEventListener("change", function () {
  wheel.style.opacity = this.value;
});
#picker {
  width: 150px;
  background: black;
}
&lt;div id="picker"&gt;
  &lt;img id="wheel" alt="" src="http://i.stack.imgur.com/TBVWx.png"&gt;
&lt;/div&gt;
&lt;input id="slider" type="range" value="1" min="0" max="1" step="0.01"&gt;




That's probably the simplest :)

Note: I haven't benchmarked any of the above. That's left as an exercise to the reader.