Tainted canvases may not be exported

I want to save my canvas to a img. I have this function:

function save() { document.getElementById("canvasimg").style.border = "2px solid"; var dataURL = canvas.toDataURL(); document.getElementById("canvasimg").src = dataURL; document.getElementById("canvasimg").style.display = "inline";
}

It gives me error:

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

What should I do?

3

13 Answers

For security reasons, your local drive is declared to be "other-domain" and will taint the canvas.

(That's because your most sensitive info is likely on your local drive!).

While testing try these workarounds:

  • Put all page related files (.html, .jpg, .js, .css, etc) on your desktop (not in sub-folders).

  • Post your images to a site that supports cross-domain sharing (like dropbox.com or GitHub). Be sure you put your images in dropbox's public folder and also set the cross origin flag when downloading the image (var img=new Image(); img.crossOrigin="anonymous" ...)

  • Install a webserver on your development computer (IIS and PHP web servers both have free editions that work nicely on a local computer).

9

In the img tag set crossorigin to Anonymous.

<img crossorigin="anonymous" />
7

If someone views on my answer, you maybe in this condition:

1. Trying to get a map screenshot in canvas using openlayers (version >= 3)
2. And viewed the example of exporting map
3. Using ol.source.XYZ to render map layer

Bingo!

Using ol.source.XYZ.crossOrigin = 'Anonymous' to solve your confuse. Or like following code:

 var baseLayer = new ol.layer.Tile({ name: 'basic', source: new ol.source.XYZ({ url: options.baseMap.basic, crossOrigin: "Anonymous" }) });

In OpenLayers6, something is changed with ES6. However, the code is similar.

import { XYZ } from 'ol/source'
import { Tile as TileLayer } from 'ol/layer'
const baseLayer = new TileLayer({ name : 'basic', source: new XYZ({ url: ' // your tile url crossOrigin: 'Anonymous', // remove this function config if the tile's src is nothing to decorate. It's usually to debug the src tileLoadFunction: function(tile, src) { tile.getImage().src = src } }) })

What's more, don't forget to set the access-control-allow-origin: * or access-control-allow-origin: [your whitelist origins] in the response header if the tiles are requested in your own server.
Like this:enter image description hereMore details, and this one

5

If you're using ctx.drawImage() function, you can do the following:

var img = loadImage('../yourimage.png', callback);
function loadImage(src, callback) { var img = new Image(); img.onload = callback; img.setAttribute('crossorigin', 'anonymous'); // works for me img.src = src; return img;
}

And in your callback you can now use ctx.drawImage and export it using toDataURL

4

In my case I was drawing onto a canvas tag from a video with something like canvas.drawImage(video, 0, 0). To address the tainted canvas error I had to do two things:

<video crossorigin="anonymous"> <source src="">
</video>
  • Ensure Access-Control-Allow-Origin header is set in the video source response (proper setup of crossdomain.example.com)
  • Set the video tag to have crossorigin="anonymous"

I resolved the problem using useCORS: true option

 html2canvas(document.getElementsByClassName("droppable-area")[0], { useCORS:true}).then(function (canvas){ var imgBase64 = canvas.toDataURL(); // console.log("imgBase64:", imgBase64); var imgURL = "data:image/" + imgBase64; var triggerDownload = $("<a>").attr("href", imgURL).attr("download", "layout_"+new Date().getTime()+".jpeg").appendTo("body"); triggerDownload[0].click(); triggerDownload.remove(); });
1

Seems like you are using an image from a URL that has not set correct Access-Control-Allow-Origin header and hence the issue.. You can fetch that image from your server and get it from your server to avoid CORS issues..

5

Check out CORS enabled image from MDN. Basically you must have a server hosting images with the appropriate Access-Control-Allow-Origin header.

<IfModule mod_setenvif.c> <IfModule mod_headers.c> <FilesMatch "\.(cur|gif|ico|jpe?g|png|svgz?|webp)$"> SetEnvIf Origin ":" IS_CORS Header set Access-Control-Allow-Origin "*" env=IS_CORS </FilesMatch> </IfModule>
</IfModule>

You will be able to save those images to DOM Storage as if they were served from your domain otherwise you will run into security issue.

var img = new Image, canvas = document.createElement("canvas"), ctx = canvas.getContext("2d"), src = ""; // insert image url here
img.crossOrigin = "Anonymous";
img.onload = function() { canvas.width = img.width; canvas.height = img.height; ctx.drawImage( img, 0, 0 ); localStorage.setItem( "savedImageData", canvas.toDataURL("image/png") );
}
img.src = src;
// make sure the load event fires for cached images too
if ( img.complete || img.complete === undefined ) { img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; img.src = src;
}
2

Just as a build on @markE's answer. You can serve your website via a local server. You won't have this error on a local server.

If you have PHP installed on your computer (some older MacOS versions has it preinstalled):

  1. Open up your terminal/cmd
  2. Navigate into the folder where your website files are
  3. While in this folder, run the command php -S localhost:3000
  4. Open up your browser and in the URL bar go to localhost:3000. Your website should be running there.

or


If you have Node.js installed on your computer:

  1. Open up your terminal/cmd
  2. Navigate into the folder where your website files are
  3. While in this folder, run the command npm init -y
  4. Run npm install live-server -g or sudo npm install live-server -g on a mac
  5. Run live-server and it should automatically open up a new tab in the browser with your website open.

Note: remember to have an index.html file in the root of your folder or else you might have some issues.

6

This one can work smoothly in laravel.

First of all, you need to convert tainted canvas to blob. after that, you can upload a blob to serve and save it as an image. Return image URL in ajax call.

Here is an ajax call to upload canvas blob.

$("#downloadCollage").click(function(){ canvas.toBlob(function(blob){ var formDataToUpload = new FormData(); formDataToUpload.append("_token", "{{ csrf_token() }}"); formDataToUpload.append("image", blob); $.ajax({ url:"{{ route('selfie_collage_upload') }}", data: formDataToUpload, type:"POST", contentType:false, processData:false, cache:false, dataType:"json", error:function(err){ console.error(err); }, success:function(data){ window.location.href= data.url; }, complete:function(){ } }); },'image/png'); link.click();
});

I also solved this error by adding useCORS : true, in my code like -

html2canvas($("#chart-section")[0], { useCORS : true, allowTaint : true, scale : 0.98, dpi : 500, width: 1400, height: 900 }).then();

In my case I was testing it from my desktop, having CORS error even after saving image locally to sub-folder.

Solution:

Moved the folder to local server WAMP in my case. Worked perfect from local server.

Note:Works only when you have saved image locally.

For anyone who still encountering the same issue from S3 even after applying the server cross-origin settings, it probably a browser caching issue. So you need to make sure to disable the caching and test again, you can do that from the browser dev-tools -> network tab -> click on disable cash option -> try again:

chrome dev-tools caching option

You Might Also Like