Reputation: 2001
I'm trying to draw a circle with, not radial gradients, but linear gradients that go around the circle... Basically, I'm trying to create a color wheel and it has to be dynamic as the colors will be customizable... However, I'm completely baffled on how to approach this matter...
I thought I could draw my own circle and color it, then loop the process with a larger radius to fill it out. But that proved to not only be extremely ineffecient but very buggy too...
Here was my first attempt: http://jsfiddle.net/gyFqX/1/ I stuck with that method but changed it to fill a 2x2 square for each point on the circle. It worked alright for blending up to 3 colors, but then you begin to notice it's distortion.
Anyway, I've continued working on it a bit and this is what I have now: http://jsfiddle.net/f3SQ2/
var ctx = $('#canvas')[0].getContext('2d'),
points = [],
thickness = 80;
for( var n = 0; n < thickness; n++ )
rasterCircle( 200, 200, (50 + n) );
function fillPixels() {
var size = points.length,
colors = [
hexToRgb( '#ff0000' ), // Red
hexToRgb( '#ff00ff' ), // Magenta
hexToRgb( '#0000ff' ), // Blue
hexToRgb( '#00ffff' ), // Teal
hexToRgb( '#00ff00' ), // Green
hexToRgb( '#ffff00' ), // Yellow
hexToRgb( '#ff0000' ), // Red
],
colorSpan = colors.length - 1;
if ( colors.length > 0 ) {
var lastPadding = size % colorSpan,
stepSize = size / colorSpan,
steps = null,
cursor = 0;
for ( var index = 0; index < colorSpan; index++ ) {
steps = Math.floor( ( index == colorSpan - 1 ) ? stepSize + lastPadding : stepSize );
createGradient( colors[ index ], colors[ index + 1 ], steps, cursor );
cursor += steps;
}
}
function createGradient( start, end, steps, cursor ) {
for ( var i = 0; i < steps; i++ ) {
var r = Math.floor( start.r + ( i * ( end.r - start.r ) / steps ) ),
g = Math.floor( start.g + ( i * ( end.g - start.g ) / steps ) ),
b = Math.floor( start.b + ( i * ( end.b - start.b ) / steps ) );
ctx.fillStyle = "rgba("+r+","+g+","+b+",1)";
ctx.fillRect( points[cursor][0], points[cursor][1], 2, 2 );
cursor++;
}
}
points = [];
}
function setPixel( x, y ) {
points.push( [ x, y ] );
}
function rasterCircle(x0, y0, radius) {
var f = 1 - radius,
ddF_x = 1,
ddF_y = -2 * radius,
x = 0,
y = radius;
setPixel(x0, y0 + radius);
while(x < y) {
if(f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
setPixel(x0 - x, y0 - y);
}
var temp = [];
f = 1 - radius,
ddF_x = 1,
ddF_y = -2 * radius,
x = 0,
y = radius;
while(x < y) {
if(f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
temp.push( [x0 - y, y0 - x] );
}
temp.push( [x0 - radius, y0] );
for(var i = temp.length - 1; i > 0; i--)
setPixel( temp[i][0], temp[i][1] );
fillPixels();
}
What I'm trying to accomplish is something like this: http://img252.imageshack.us/img252/3826/spectrum.jpg
The 'brightness' (white to black fade) is not an issue as I know it can be accomplished by using a radial gradient after the color spectrum has been drawn. However, I'd appreciate some help in figuring out how to draw the spectrum itself.
I was even thinking I could draw a linear one and then bend (transform) it, but there aren't any native functions to do that and tackling something such as that is above my skill level. :-/
Upvotes: 1
Views: 3408
Reputation: 13967
Check this out: http://jsfiddle.net/f3SQ2/5/
var can = $('#canvas')[0],
ctx = can.getContext('2d'),
radius = 120,
thickness = 80,
p = {
x: can.width,
y: can.height
},
start = Math.PI,
end = start + Math.PI / 2,
step = Math.PI / 180,
ang = 0,
grad,
r = 0,
g = 0,
b = 0,
pct = 0;
ctx.translate(p.x, p.y);
for (ang = start; ang <= end; ang += step) {
ctx.save();
ctx.rotate(-ang);
// linear gradient: black->current color->white
grad = ctx.createLinearGradient(0, radius - thickness, 0, radius);
grad.addColorStop(0, 'black');
h = 360-(ang-start)/(end-start) * 360;
s = '100%';
l = '50%';
grad.addColorStop(.5, 'hsl('+[h,s,l].join()+')');
grad.addColorStop(1, 'white');
ctx.fillStyle = grad;
// the width of three for the rect prevents gaps in the arc
ctx.fillRect(0, radius - thickness, 3, thickness);
ctx.restore();
}
Edit: fixed color spectrum. Apparently we can just give it HSL values, no need for conversions or messy calculations!
Modified a few things to handle scaling better: http://jsfiddle.net/f3SQ2/6/
step = Math.PI / 360
ctx.fillRect(0, radius - thickness, radius/10, thickness);
You could for example set the gradient stops like so:
h = 360-(ang-start)/(end-start) * 360;
s = '100%';
grad.addColorStop(0, 'hsl('+[h,s,'0%'].join()+')'); //black
grad.addColorStop(.5,'hsl('+[h,s,'50%'].join()+')'); //color
grad.addColorStop(1, 'hsl('+[h,s,'100%'].join()+')');//white
Upvotes: 4
Reputation: 7170
My first note would be that the image you linked to has all 3 components it doesn't need to change and could just be a static image.
I adapted some code from a project i'm working on: http://jsfiddle.net/f3SQ2/1/
function drawColourArc(image) {
var data = image.data;
var i = 0;
var w = image.width, h = image.height;
var result = [0, 0, 0, 1];
var outer = 1, inner = 0.5;
var mid = 0.75;
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var dx = (x / w) - 1, dy = (y / w) - 1;
var angular = ((Math.atan2(dy, dx) + Math.PI) / (2 * Math.PI)) * 4;
var radius = Math.sqrt((dx * dx) + (dy * dy));
if (radius < inner || radius > outer) {
data[i++] = 255;
data[i++] = 255;
data[i++] = 255;
data[i++] = 0;
}
else {
if (radius < mid) {
var saturation = 1;
var brightness = (radius - 0.5) * 4;
}
else {
var saturation = 1- ((radius - 0.75) * 4);
var brightness = 1;
}
result[0] = angular;
result[1] = saturation;
result[2] = brightness;
result[3] = 1;
//Inline HSBToRGB
if (result[1] == 0) {
result[0] = result[1] = result[2] = result[2];
}
else {
var varH = result[0] * 6;
var varI = Math.floor(varH); //Or ... var_i = floor( var_h )
var var1 = result[2] * (1 - result[1]);
var var2 = result[2] * (1 - result[1] * (varH - varI));
var var3 = result[2] * (1 - result[1] * (1 - (varH - varI)));
if (varI == 0 || varI == 6) {
result[0] = result[2];
result[1] = var3;
result[2] = var1;
}
else if (varI == 1) {
result[0] = var2;
result[1] = result[2];
result[2] = var1;
}
else if (varI == 2) {
result[0] = var1;
result[1] = result[2];
result[2] = var3;
}
else if (varI == 3) {
result[0] = var1;
result[1] = var2;
result[2] = result[2];
}
else if (varI == 4) {
result[0] = var3;
result[1] = var1;
result[2] = result[2];
}
else {
result[0] = result[2];
result[1] = var1;
result[2] = var2;
}
}
//End of inline
data[i++] = result[0] * 255;
data[i++] = result[1] * 255;
data[i++] = result[2] * 255;
data[i++] = result[3] * 255;
}
}
}
};
var canvas = document.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext("2d");
var image = ctx.createImageData(canvas.width, canvas.height);
drawColourArc(image);
ctx.putImageData(image, 0, 0);
This does it per-pixel which is accurate but you may want to draw an outline to combat the aliasing. It could be adapted to use custom colours instead of interpolating hue.
Upvotes: 1