Photo by Igor Miske

Manipulating cross origin images with HTML canvas

HTML canvas interface implements a series of security related features that prevent cross domain manipulation unless explicitly allowed by the origin domain.

What does that mean in practice? In order to be able to read the pixels data of an image on a different domain, the hosting server must first declare the appropriate Access-Control-Allow-Origin header. Once done, the consumer will be able to load the image into a canvas:

var img = new Image(),
canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d"),
src = "http://example.com/image.jpg";
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
}
img.src = src;

However, unfortunately, that is not enough. If we try to call canvas.toDataURL() the browser will complain and raise a security error:

Uncaught SecurityError: Failed to execute ‘toDataURL’ on ‘HTMLCanvasElement’: Tainted canvases may not be exported.

Whaaat?? The image has already been rendered on the canvas, so the data must be there! True, but the HTML5 spec has introduced another security related attribute on the img tag. We have to manually set the crossOrigin attribute, even when using the Image constructor, in order to allow the browser to read the pixel data back from the canvas. The annoying bit is we cannot do it “by default”, as there are two values that can be set, depending on the context: anonymous, which fetches images without sending cookies or auth headers to the third party domain, or use-credentials, which sends cookies and auth headers upon request.

So, it is best to check whenever the source is cross origin and set the attribute accordingly based on your needs:

var img = new Image(),
canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d"),
src = "http://example.com/image.jpg";
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
console.log(canvas.toDataUrl());
}
// check if //domain.com or http://domain.com is a different origin
if (/^([\w]+\:)?\/\//.test(src) && src.indexOf(location.host) === -1) {
img.crossOrigin = "anonymous"; // or "use-credentials"
}
img.src = src;
Like what you read? Give Alberto Gasparin a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.