0

I want to make a gradient that covers the whole canvas whatever the angle of it.

So I used a method found on a Stack Overflow post which is finally incorrect. The solution is almost right but, in fact, the canvas is not totally covered by the gradient.

It is this answer: https://stackoverflow.com/a/45628098/5594331 (You have to look at the last point named "Example of best fit.")

In my code example below, the yellow part should not be visible because it should be covered by the black and white gradient. This is mostly the code written in Blindman67's answer with some adjustments to highlight the problem.

I have drawn in green the control points of the gradient. With the right calculations, these should be stretched to the edges of the canvas at any angle.

enter image description here

var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;

function bestFitGradient(angle){
    
    var dist = Math.sqrt(w * w + h * h) / 2; // get the diagonal length    
    var diagAngle = Math.asin((h / 2) / dist); // get the diagonal angle

    // Do the symmetry on the angle (move to first quad
    var a1 = ((angle % (Math.PI *2))+ Math.PI*4) % (Math.PI * 2);
    if(a1 > Math.PI){ a1 -= Math.PI }
    if(a1 > Math.PI / 2 && a1 <= Math.PI){ a1 = (Math.PI / 2) - (a1 - (Math.PI / 2)) }
    // get angles from center to edges for along and right of gradient
    var ang1 = Math.PI/2 - diagAngle - Math.abs(a1);
    var ang2 = Math.abs(diagAngle - Math.abs(a1));
    
    // get distance from center to horizontal and vertical edges
    var dist1 = Math.cos(ang1) * h;
    var dist2 = Math.cos(ang2) * w;
    
    // get the max distance
    var scale = Math.max(dist2, dist1) / 2;
    
    // get the vector to the start and end of gradient
    var dx = Math.cos(angle) * scale;
    var dy = Math.sin(angle) * scale;
    
    var x0 =  w / 2 + dx;
    var y0 =  h / 2 + dy;
    var x1 =  w / 2 - dx;
    var y1 =  h / 2 - dy;
    
    // create the gradient
    const g = ctx.createLinearGradient(x0, y0, x1, y1);
    
    // add colours
    g.addColorStop(0, "yellow");
    g.addColorStop(0, "white");
    g.addColorStop(.5, "black");
    g.addColorStop(1, "white");
    g.addColorStop(1, "yellow");
    
    return {
      g: g,
      x0: x0,
      y0: y0,
      x1: x1,
      y1: y1
   };
}   

function update(timer){
    var r = bestFitGradient(timer / 1000);
    
    // draw gradient
    ctx.fillStyle = r.g;
    ctx.fillRect(0,0,w,h);
    
    // draw points
    ctx.lineWidth = 3;
    ctx.fillStyle = '#00FF00';
    ctx.strokeStyle = '#FF0000';
    ctx.beginPath();
    ctx.arc(r.x0, r.y0, 5, 0, 2 * Math.PI, false);
    ctx.stroke();
    ctx.fill();
    ctx.beginPath();
    ctx.arc(r.x1, r.y1, 5, 0, 2 * Math.PI, false);
    ctx.stroke();
    ctx.fill();
    
    requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
  border : 2px solid red;
}
<canvas id="canvas" width="300" height="200"></canvas>
Dady
  • 71
  • 8

1 Answers1

1

In this fiddle there is a function that calculates the distance between a rotated line and a point:

function distanceToPoint(px, py, angle) {
  const cx = width / 2;
  const cy = height / 2;
  return Math.abs((Math.cos(angle) * (px - cx)) - (Math.sin(angle) * (py - cy)));
}

Which is then used to find the maximum distance between the line and the corner points (only two points are considered, because the distances to the other two points are mirrored):

const dist = Math.max(
  distanceToPoint(0, 0, angle),
  distanceToPoint(0, height, angle)
);

Which can be used to calculate offset points for the end of the gradient:

const ox = Math.cos(angle) * dist;
const oy = Math.sin(angle) * dist;
const gradient = context.createLinearGradient(
  width / 2 + ox,
  height / 2 + oy,
  width / 2 - ox,
  height / 2 - oy
)
aptriangle
  • 1,395
  • 1
  • 9
  • 11
  • This is exactly what I was looking for. Thank you so much for your help. Do you know why Blindman67's answer is titled "Best fit"? The calculations are much simpler with your answer and seems to me more consistent for a "best fit" gradient, no? – Dady Jan 09 '23 at 06:28