Merge Multiple Canvases and Download as Image

Alex Saidani picture Alex Saidani · Apr 10, 2015 · Viewed 12k times · Source

I'm attempting to merge two HTML canvases into a single canvas and then download that as an image. My code is as below:

function downloadCanvas() {
    var bottleCanvas = document.getElementById('bottleCanvas');
    var designCanvas = document.getElementById('editorCanvas');

    var bottleContext = bottleCanvas.getContext('2d');
    bottleContext.drawImage(designCanvas, 69, 50);

    var dataURL = bottleCanvas.toDataURL("image/png");
    var link = document.createElement('a');
    link.download = "bottle-design.png";
    link.href = bottleCanvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
    link.click();
}

My problem here seems to be the following line:

bottleContext.drawImage(designCanvas, 69, 50);

This is supposed to draw the contents of one canvas onto the other, which it does, but then prevents the latter part of the code from running and downloading the image. When I remove this particular line the download function works fine, but unfortunately only downloads one of the canvases.

My question therefore is either: What am I doing wrong here? or How would I merge two HTML canvases and then download it as an image.

(On another note, my above code for downloading only works well in Chrome - in other browsers I am unable to set the name of the file and set the file extension.)

Answer

markE picture markE · Apr 10, 2015

You are likely encountering a security error caused by drawing an image whose source is cross-domain onto your canvas. Drawing a cross-domain image onto any canvas will "taint" that canvas and disallow context.toDataURL and raise a security error if you attempt to execute toDataURL. This same "tainting" will occur if you drawImage a contaminated canvas onto a non-contaminated canvas.

The fix is to make sure all images you draw on the canvas originate on the same domain as your webpage.

Here is an example of your code working properly when using an image that does not raise the cross-domain security error:

var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/fish.jpg";
function start(){

  var bottleCanvas = document.getElementById('bottleCanvas');
  var designCanvas = document.getElementById('editorCanvas');
  var ctxb=bottleCanvas.getContext('2d');
  var ctxd=editorCanvas.getContext('2d');

  ctxb.drawImage(img,0,0);
  ctxd.fillRect(50,50,50,50);

  downloadCanvas();
}

function downloadCanvas() {
  var bottleCanvas = document.getElementById('bottleCanvas');
  var designCanvas = document.getElementById('editorCanvas');

  var bottleContext = bottleCanvas.getContext('2d');
  bottleContext.drawImage(designCanvas, 69, 50);

  var dataURL = bottleCanvas.toDataURL("image/png");
  var link = document.createElement('a');
  link.download = "bottle-design.png";
  link.href = bottleCanvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
  link.click();
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<canvas id="bottleCanvas" width=300 height=300></canvas>
<canvas id="editorCanvas" width=300 height=300></canvas>

Satisfying cross-origin security restrictions

You can host your image on a server that already allows cross-origin access to it's images. That's what I do in my example above. Dropbox.com allows you to specify that an image it hosts may be drawn to canvas without "tainting" that canvas.

You can also configure your S3 bucket to allow cross-origin access to your images. This link provides instructions on how to set the response headers to server cross-origin images: http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html

Note that as in my example, if you're using cross-origin images, you must also set the image.crossOrigin='anonymous' flag when you initially create the image object in javascript.