What you need is linear interpolation of colours to give that smooth transition from one color to another. I'll give a simple workout for you to understand the math involved.
Red = (1, 0, 0)
Yellow = (1, 1, 0) (lies exactly between red and yellow)
Green = (0, 1, 0)
First interpolate from red to yellow. Since x
and z
stay the same, the only component to interpolate is y
. The smoothness depends on how many stops you make from one extreme to the other. Say we take stops = 4
+------------+------------+
|Red |(1, 0, 0) |
+------------+------------+
| |(1, 0.2, 0) |
+ +------------+
| |(1, 0.4, 0) |
+ Yellowish +------------+
| Red |(1, 0.6, 0) |
+ +------------+
| |(1, 0.8, 0) |
+------------+------------+
|Yellow |(1, 1, 0) |
+------------+------------+
| |(0.8, 1, 0) |
+ +------------+
| |(0.6, 1, 0) |
+ Yellowish +------------+
| Green |(0.4, 1, 0) |
+ +------------+
| |(0.2, 1, 0) |
+------------+------------+
|Green |(0, 1, 0) |
+------------+------------+
If you interpolate from Red -> Yellow -> Green -> Cyan -> Blue -> Magenta
you'd get a line with one extreme being Red and the other being Magenta.
Now to create the HSV wheel you've posted in your question, one needs to do both radial and axial interpolaton.
Hue (or the actual colour) is radially interpolated i.e. based on the angle. The smoothness (stops) here would be based on angle instead of distance.
+-------+-------+
|Angle° |Colour |
+-------+-------+
|0/360 |Red |
+-------+-------+
|60 |Yellow |
+-------+-------+
|120 |Green |
+-------+-------+
|180 |Cyan |
+-------+-------+
|240 |Blue |
+-------+-------+
|300 |Magenta|
+-------+-------+
Saturation (vividness) is interpolated radially i.e. based on the point's distance from the centre. The centre is pure white while the point at the circumference is pure colour.
So the line from centre to circumference at angle 0° would start with white (1, 1, 1) at centre and end with pure red (1, 0, 0) at circumference. Do the same for the other angles too (until you circle back to 360/0°) and you'd get the wheel you've posted; it's actually a disc from a greater (HSV) cylinder whose vertical axis would interpolate Lightness (picture in Wikipedia's excellent article linked above).
See code below for a live rendering of a HSV wheel.
// REFERENCES
// http://stackoverflow.com/q/10373695
// http://stackoverflow.com/a/7541756
// http://tutorials.jenkov.com/html5-canvas/gradients.html
// Computer Graphics: From Pixels to Programmable Graphics
// Hardware by Alexey Boreskov, Evgeniy Shikin
// from rgb_hsv_lerp workout
function hsv2rgb(h) {
var s = 1, v = 1;
while(h < 0) h += 360;
while(h >= 360) h -= 360;
h /= 60;
var i = Math.floor(h);
var f = h - i;
var p = v * (1 - s);
var q = v * (1 - (s * f));
var t = v * (1 - (s * (1 - f)));
rgb = [];
switch(i) {
case 0: rgb[0] = v; rgb[1] = t; rgb[2] = p; break;
case 1: rgb[0] = q; rgb[1] = v; rgb[2] = p; break;
case 2: rgb[0] = p; rgb[1] = v; rgb[2] = t; break;
case 3: rgb[0] = p; rgb[1] = q; rgb[2] = v; break;
case 4: rgb[0] = t; rgb[1] = p; rgb[2] = v; break;
case 5: rgb[0] = v; rgb[1] = p; rgb[2] = q; break;
}
return "rgb(" + Math.floor(255 * rgb[0]) + ","
+ Math.floor(255 * rgb[1]) + ","
+ Math.floor(255 * rgb[2]) + ")";
}
function plot(ctx, x, y, hue) {
var cx = 0, cy = 0;
var xs = [x, -y, -x, y];
var ys = [y, x, -y, -x];
for (var i = 0; i < 4; ++i) {
// linear gradient from start to end of line
var grad = ctx.createLinearGradient(0, 0, xs[i], ys[i]);
grad.addColorStop(0, "white");
// the canvas coordinate system has Y ↓ while math coordinate
// has Y ↑, so flip hue too to match standard HSV wheel
grad.addColorStop(1, hsv2rgb(-(hue + (90 * i))));
ctx.strokeStyle = grad;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(xs[i], ys[i]);
ctx.stroke();
}
}
function draw() {
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
var r = can.clientWidth / 2;
ctx.translate(r, r);
ctx.lineWidth = 2;
var x = r, y = 0;
plot(ctx, x, y, 0);
// divide 45° by number of iterations to get iteration delta
// y starts at 0, iteration ends when y = x i.e. x = y = r / √2
var hue_delta = 45 / Math.floor(r / Math.sqrt(2));
// Bresenham's circle drawing algorithm, reference book cited above
var d = 1.25 - r;
while (x > y) {
++y;
if (d < 0)
d += (2 * y) + 3;
else {
d += 2 * (y - x) + 5;
--x;
}
var hue = y * hue_delta;
plot(ctx, x, y, hue);
plot(ctx, y, x, 90 - hue);
}
ctx.translate(-r, -r);
}
<body onload="draw()">
<h1>HSV Colour Wheel</h1>
<canvas id="canvas1" width="300" height="300" />
</body>