Search code examples
javascriptcanvasaspect-ratioradial-gradients

Alter the dimensions of the canvas image permanently and NOT keep the aspect ratio


I am creating a gradient on a canvas, generating a PNG file, and then downloading it. I want a radial gradient which is an ellipse. Since canvas gradients must be circles, I am generating the gradient as a circle and then resizing the canvas element to create the ellipse. This works fine and the visually generated image is the ellipse I want.

When I go to download the generated canvas, it is using the full size of the canvas, not the css styled size and so I get the original full sized circle. Is there a way to actually alter the dimensions of the canvas image and NOT keep the aspect ratio? All the SO posts that show up on the topic want to keep the aspect ratio.

makeCanvas();
scaled();

var input = document.createElement("input");
input.type = "button";
input.style.display = "block";
input.value = "Download Canvas to PNG";
input.onclick = function() {
  var canvas = document.getElementById("canvas");
  downloadURI(canvas.toDataURL("image/png"), "gradient.png");
}
document.body.appendChild(input);

var input = document.createElement("input");
input.type = "button";
input.style.display = "block";
input.value = "Download Scaled";
input.onclick = function() {
  var canvas = document.getElementById("scaled");
  downloadURI(canvas.toDataURL("image/png"), "gradient.png");
}
document.body.appendChild(input);

function makeCanvas() {
  var canvasGradient;
  var ctx;
  width = 200;
  height = 200;
  var canvas = document.createElement("canvas");
  canvas.id = "canvas";
  canvas.style.width = width + "px";
  canvas.style.height = height + "px";
  canvas.width = width;
  canvas.height = height;
  ctx = canvas.getContext('2d');

  var gradient;
  // both circles have to share the center of the canvas
  var coords = {
    x1: 0,
    y1: 0,
    x2: 0,
    y2: 0
  };
  coords.x1 = (width / 2);
  coords.x2 = coords.x1;
  coords.y1 = (height / 2);
  coords.y2 = coords.y1;
  // inner circle is just the center - a point
  coords.r1 = 0;
  // outer circle is the edge
  coords.r2 = Math.min(height, width);
  gradient = ctx.createRadialGradient(coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2);
  gradient.addColorStop(0, "white");
  gradient.addColorStop(1, "black");
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, width, height);
  canvas.style.width = width / 2 + "px";
  document.body.append(canvas);

  can = alter(canvas, ctx);
  //document.body.append(can);
}

function downloadURI(uri, name) {
  var link = document.createElement("a");
  link.download = name;
  link.href = uri;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  delete link;
}

function alter(canvas, ctx) {
  var can = document.createElement("canvas");
  can.id = "can";
  can.width = Math.floor(canvas.width / 2);
  can.height = canvas.height;
  can.ctx = can.getContext("2d");
  can.ctx.drawImage(canvas, 0, 0, can.width, can.height);
  canvas.width = can.width;
  canvas.height = can.height;
  ctx.drawImage(can, 0, 0);
  return can;
}

function scaled() {
   var canvas = document.createElement("canvas");
  canvas.id = "scaled";
  canvas.style.width = "200px";
  canvas.style.height = "200px";
  canvas.width = 200;
  canvas.height = 200;
  ctx = canvas.getContext('2d');

  var gradient;
 var g = ctx.createRadialGradient(100, 100, 0, 100, 100, 100);
  g.addColorStop(0, "white")
  g.addColorStop(1, "red")
  ctx.fillStyle = g
  ctx.scale(0.5, 1) // scale x axis 0.5
  ctx.fillRect(0, 0, 200, 200) // gradient circle squashed along x  
   document.body.append(canvas);
}


Solution

  • Use transformations to change shape of gradients.

    For your info gradient circles can be transformed to a ellipse.

    var g = ctx.createRadialGradient(200,200,0,200,200,200)
    g.addColorStop(0,"red")
    g.addColorStop(1,"white")
    ctx.fillStyle = g
    ctx.scale(0.5,1) // scale x axis 0.5
    ctx.fillRect(0,0,400,400)  // gradient circle squashed along x
    

    Scaling a canvas

    Create a second canvas

    // assuming canvas , ctx is the original canvas and context
    var can = document.createElement("canvas");
    

    Set the resolution

    can.width = Math.floor(canvas.width / 2);
    can.height = canvas.height;
    

    Get context and draw original on new canvas

    can.ctx = can.getContext("2d");
    can.ctx.drawImage(canvas,0,0,can.width,can.height);
    

    Set the original canvas to the new size

    canvas.width = can.width;
    canvas.height = can.height;
    

    Draw on the scaled can onto the original canvas

    ctx.drawImage(can,0,0);
    can = undefined; // reference to dump temp canvas
    

    You now have the canvas scale yet keeping the content. You could also just replace the old canvas with the new one