How can I generate a rainbow circle using HTML5 canvas?
Asked Answered
R

3

5

I would like to generate a canvas image using gradients in some clever way. I would like the image to looks something like this:

rainbow circle

I just can't get my head around it. I need to generate lines in the form and arc - or use gradients with color stops in some clever way. Maybe it would be a lot easier if I converted to HSL and just go through the HUE values?

For example in a rectangle format I could

for (var i = 0; i < h; ++i) {
  var ratio = i/h;
  var hue = Math.floor(360*ratio);
  var sat = 100;
  var lum = 50;
  line(dc, hslColor(hue,sat,lum), left_margin, top_margin+i, left_margin+w, top_margin+i);
}

Does anybody have any clever tips on how to produce this image using canvas?

Rigobertorigor answered 15/11, 2013 at 9:41 Comment(0)
Z
16

This is not perfect (due to drawing steps ...), but it can help you :

http://jsfiddle.net/afkLY/2/

HTML:

<canvas id="colors" width="200" height="200"></canvas>

Javascript:

var canvas = document.getElementById("colors");
var graphics = canvas.getContext("2d");

var CX = canvas.width / 2,
    CY = canvas.height/ 2,
    sx = CX,
    sy = CY;

for(var i = 0; i < 360; i+=0.1){
    var rad = i * (2*Math.PI) / 360;
    graphics.strokeStyle = "hsla("+i+", 100%, 50%, 1.0)";   
    graphics.beginPath();
    graphics.moveTo(CX, CY);
    graphics.lineTo(CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
    graphics.stroke();
}

The idea is to draw the disc line by line with a hue value corresponding to the line direction.

You can change the color base rotation by adding a radius angle to rad variable (adding -pi/2 to rad would make the gradient look like your figure).

EDIT: I made a new demo that generalizes the concept a bit and renders a rainbow polygon. Here is the CodePen. To get rid of the small voids beteween the colors, I used quads that overflow to the next color part, except for the last one.

Zip answered 15/11, 2013 at 10:2 Comment(7)
Well, in this case it is. But the step must be function of the disc size, otherwise the drawing is not complete. Let's try 2000x2000 for canvas size without changing the code : jsfiddle.net/afkLY/4 This is an extreme case, but a case anyway.Zip
This looks really great! Thanks, can you elaborate even more on what you mean about the drawing steps? What do you mean that the step must be a function of the disc size?Rigobertorigor
A "real" disc has an infinite number of radii (or diameters), that's why it is totally filled. When you want to draw it digitally, you cannot draw an infinite number of lines to fill it. You have to choose a drawing resolution (step) which is enough accurate to entirely fill the disc.Zip
Yes - it definitely needs to be quantized - and changing that step value (0.1) will introduce interesting artifacts. It surely needs to be variable to the size of the disc. How did you decide on 0.1? How could I calculate the largest possible step that won't introduce articafts related to the disc diameter size?Rigobertorigor
I think that it depends on the algorithm used to draw the lines, I tried to make a formula based on the disc perimeter like : step = 360 / (2 x 2 x pi x radius) wich make twice more lines than perimeter unit (and seems to work with 4000x4000 disc). But I'm not so clever in this field to give you the theorical answer.Zip
The angle at which we see a height of one at a distance radius is the arctan(1/radius), which is well enough approximated by 1/radius. Notice that it is pies that you want to draw, not lines, to avoid this strange rotation effect near the center. Approximating pies to triangles is good enough for small pies. With bigger pies, you create visible shades. fiddle is here :jsfiddle.net/gamealchemist/afkLY/11/http://jsfiddle.net/…Slap
Just want to say that I am really impressed with this jsFiddle. I would love to be able to do stuff like this.Dobla
U
1

Small adjustment to make it have a white center

var canvas = document.getElementById('colorPicker'); var graphics = canvas.getContext("2d");

        var CX = canvas.width / 2,
            CY = canvas.height / 2,
            sx = CX,
            sy = CY;

        for (var i = 0; i < 360; i += 0.1) {
            var rad = i * (2 * Math.PI) / 360;
            var grad = graphics.createLinearGradient(CX, CY, CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
            grad.addColorStop(0, "white");
            grad.addColorStop(0.01, "white");
            grad.addColorStop(0.99, "hsla(" + i + ", 100%, 50%, 1.0)");
            grad.addColorStop(1, "hsla(" + i + ", 100%, 50%, 1.0)");
            graphics.strokeStyle = grad;
            graphics.beginPath();
            graphics.moveTo(CX, CY);
            graphics.lineTo(CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
            graphics.stroke();
        }
Unexampled answered 18/10, 2015 at 20:47 Comment(0)
M
0

Here is an alternate approach that takes a slightly more functional approach:

var canvas = document.getElementById("radial"),
    ctx = canvas.getContext("2d"),
    width = canvas.width,
    height = canvas.height,
    center = { x: width/2, y: height/2 },
    diameter = Math.min(width, height);

var distanceBetween = function(x1,y1,x2,y2) {
  // Get deltas
  var deltaX = x2 - x1,
      deltaY = y2 - y1;

  // Calculate distance from center
  return Math.sqrt(deltaX*deltaX+deltaY*deltaY);  
}

var angleBetween = function(x1,y1,x2,y2) {
  // Get deltas
  var deltaX = x2 - x1,
      deltaY = y2 - y1;

  // Calculate angle
  return Math.atan2(deltaY, deltaX);
}

var radiansToDegrees = _.memoize(function(radians) {
    // Put in range of [0,2PI)
  if (radians < 0) radians += Math.PI * 2;

  // convert to degrees
  return radians * 180 / Math.PI; 
})

// Partial application of center (x,y)
var distanceFromCenter = _.bind(distanceBetween, undefined, center.x, center.y)
var angleFromCenter = _.bind(angleBetween, undefined, center.x, center.y)

// Color formatters
var hslFormatter = function(h,s,l) { return "hsl("+h+","+s+"%,"+l+"%)"; },
    fromHue = function(h) { return hslFormatter(h,100,50); };

// (x,y) => color
var getColor = function(x,y) {
  // If distance is greater than radius, return black
  return (distanceFromCenter(x,y) > diameter/2)
    // Return black
    ? "#000"
    // Determine color
    : fromHue(radiansToDegrees(angleFromCenter(x,y)));
};

for(var y=0;y<height;y++) {
  for(var x=0;x<width;x++) {
    ctx.fillStyle = getColor(x,y);
    ctx.fillRect( x, y, 1, 1 );
  }
}

It uses a function to calculate the color at each pixel – not the most efficient implementation, but perhaps you'll glean something useful from it.

Note it uses underscore for some helper functions like bind() – for partial applications – and memoize.

Codepen for experimentation.

Metalinguistics answered 15/11, 2013 at 10:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.