简体   繁体   中英

HTML5 Canvas - Drawing Linear Gradients on a Circle (Color Wheel)

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. :-/

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM