Tainted canvases may not be exported
Asked Answered
M

18

319

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?

Mimosa answered 28/3, 2014 at 10:45 Comment(4)
In what browser? https://mcmap.net/q/101043/-blank-png-tainted-canvases-may-not-be-exported-not-using-images claims this is a bug.Wainscoting
on chrome and on firefoxMimosa
developer.mozilla.org/en-US/docs/Web/HTML/…Sugary
i solved! just try in localhostUncinus
C
295

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).

Curio answered 28/3, 2014 at 15:23 Comment(10)
Thanks, setting the img.crossOrigin property helped me!Sojourn
@Curio - I loaded image data from localStorage instead of loading from file or any url, then did some manipulation to it like adding a text. Then tried to sotre back it to localStorage using toDataURL(). But it shows "Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported". In this case I am not using any exteranl file or url to get a cross domain issue. Then why it is resulting in this error?Scuff
@Saijth - You may want to verify the path used for the images. I had this problem as well because I was testing directly accesing my local virtual server thru its IP (127.0.x.x/) but some of the images were linked thru the domain (localhost/). Once I used the localhost instead it worked out. So make sure you arent running into something like that.Idolah
(1) View it from xampp webserver, localhost/file instead of c:/localdisk/file; then chrome won't complain about Security Error. (2) Or use this flag when starting chrome: --allow-file-access-from-filesLap
see crossOrigin option in ol.source.XYZ if you using openlayers 3 or 4 API DOCConsiderate
Just adding another possible problem: if you're trying to export a canvas that contains a svg with a ForeignObject, some browsers will mark it as tainted.Chafer
@Chafer then how can we solve this type of problem? I was trying to code to export the canvas created from external image URL. I couldn't export it.Valera
@PravinWork Here's another option: you can cache the image with Cache APIVega
For option #3 (running local server) if you have python installed just run: python -m http.server 8000 and then you can go to localhost:8000 in browser.Paraglider
Why would it fail in sub-folders? At least it looks like it is impossible to fix the issue if we use file:///, we have to setup a server instead.Extinguisher
D
208

In the img tag set crossorigin to Anonymous.

<img crossorigin="anonymous" />
Dali answered 27/7, 2015 at 12:50 Comment(7)
But what to do in the case of html5 canvas , not img elementsChanna
In the case of a canvas element, the source of the problem is always with some image (or images) that you're drawing onto it. So you just need to track down the image and set its crossOrigin attribute as indicated, before loading it.Insightful
<img crossorigin="Anonymous"> works well with html2canvas(element.nativeElement, {useCORS: true}) .then(canvas => { const dataUrl = canvas.toDataURL('image/png'); })Weywadt
Don't know if there was some kind of difference between the two off hand. But <img origin="Anonymous"> is what worked for me. ( I use chrome )Philae
It's worth noting that setting image.crossOrigin = 'Anonymous' won't help once the image has been loaded. But if a reload is an option you can go for something like: document.querySelectorAll('img').forEach(image => { image.crossOrigin = 'Anonymous'; image.src += ' '; }). See also this article on MDN for reference.Singhal
This didn't work for me as all lowercase, but crossOrigin worked.Ruffi
Sorry but what is </img> ? Removed from your Answer. IMG is a void element and does not uses a closing tag.Yawp
T
44

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

Trapezoid answered 16/7, 2017 at 14:37 Comment(5)
This did not work for me. Still getting the Tainted canvases may not be exported. error message.Monniemono
it works for me. thanks. @SamSverko make sure set the attribute before img.src.Sigismundo
Doesn't work. Says blocked by CORS policyFelder
as been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. The FetchEvent for "img.Holmes
img.setAttribute('crossorigin', 'anonymous'); ✅ work well img.crossorigin = 'anonymous'; ❌ not workLattermost
C
43

You may be 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: 'example.tile.com/x/y/z', // 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 here

More details, and this one

Considerate answered 9/10, 2017 at 2:13 Comment(5)
fantastic. Useful for all sources, like TileImage and such as well.Coelho
Fatastic! Exactly what I was looking for myself, easy fix for an OpenLayers demo Im doing.Socialist
I'm using vector tile layers and after add this propery to source but it is not working too!Dottydoty
I'm using ol6. but its not working , No change after adding this lineHomophile
@JayCummins, I add the OL6 code. It maybe helps you.Considerate
C
30

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 id="video_source" crossorigin="anonymous">
    <source src="http://crossdomain.example.com/myfile.mp4">
</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"
Cressy answered 30/11, 2017 at 21:33 Comment(0)
C
15

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();
    });
Crackdown answered 21/10, 2019 at 10:31 Comment(2)
How is html2canvas library related to OP's question? =DPrefatory
It is related because this lib created a new element or something and causes CORS issue. The param useCORS: true workedDawna
V
12

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..

Vasoconstrictor answered 28/3, 2014 at 12:40 Comment(5)
Could you possible be more precise about your answer, because im not really know all that concept . How do i fetch that image from my server?Mimosa
from where are you fetching that image, is it from your server or some other one?Vasoconstrictor
It goes like this : loadImage("example.jpg", 0, 0, 500, 300); I can put random image url or image on the same folder in my computer, its still the sameMimosa
yes, but i just created image on paint and it still is the sameMimosa
I gave Access-Control-Allow-Origin:* for the file, but still it is showing the errorConjunction
B
6

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 = "http://example.com/image"; // 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;
}
Bollen answered 24/11, 2017 at 4:56 Comment(2)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewCalculate
Thanks for the notice. I just add the content from the MDN link:)Bollen
J
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();
});
Juan answered 17/9, 2021 at 8:21 Comment(2)
Im getting an error "canvas is not defined" and I cant figure out how to solve itIliad
@LaurențiuCozma here into my code, the canvas is a variable. for example canvas = document.getElementById('my-canvas');Juan
M
5

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.

Mislay answered 25/3, 2020 at 15:27 Comment(6)
Mac OS does NOT have php pre-installed. I have macos and it has nothing about PHP. If you have php, you or someone else installed it.Kalinin
That's not true @LukasLiesis. A simple Google search will show you that it is: google.com/…. All the Macs I've ever worked on had it preinstalled. If you have Big Sur installed as the default (not as an update), you might need to activate it (although I didn't have to): google.com/….Mislay
In the terminal you can run php --version to see what version you have installed.Mislay
Go get new mac, run your apache daemon script which is referenced in the article and you will see PHP is not part of mac. Unless you run some old version of OS, which is never a good idea, at least on mac. The line about Apache's php module is not even in httpd.conf, but seems like it was there on older OSes. There is a line with comment: #PHP was deprecated in macOS 11 and removed from macOS 12 Both of your linked articles are just wrong. php --version says command not found, while php is not part of mac :)Kalinin
As a matter of fact I have a brand new macbook that I bought a month ago and it still has PHP preinstalled and activated. PHP worked right off the bat. The only thing I can note is that I chose an intel chip and not the M1 chip (for compatibility with some of the software I'm using). No one said it's part of mac. I said it comes preinstalled, which it does :) Although when you check the version of PHP in the terminal it does print a warning saying—and I quote: "Future versions of macOS will not include PHP." Here's a screenshot: imgur.com/a/ZZL1SwNMislay
And the truth is that it doesn't even matter if it is preinstalled or not. The fact remains that if you have PHP installed you can use the command php -S localhost:3000 to serve your website and then you won't get the error that the OP is asking about. That's the point of the post. If PHP is not installed on your computer and you want to use it instead of Node.js, then simply download and install it. And when all the old macOS versions with PHP have been phased out of circulation, then I'll update this post to reflect that—no problem—but for now I'll keep it as "older versions" :)Mislay
T
4

tl;dr

This issue made me crazy and solved it by loading image with crossOrigin="anonymous" before rendering canvas.

Detailed and too-specific solution

For those who uses React + canvg + Amazon S3 and want to export svg as png via canvas, this could be useful.

First, create a React hook to detect preloading cross-origin images:

// useLoadCrossOriginImage.tsx

import { useReducer, useMemo } from 'react'

export function useLoadCrossOriginImage(imageUrls: string[]) {
  const [count, increase] = useReducer((count) => count + 1, 0)

  const render = () =>
    imageUrls.map((url) => (
      <img
        src={url}
        crossOrigin="anonymous"
        onLoad={increase}
        className="hidden"
      />
    ))

  const loaded = useMemo(() => count === imageUrls.length, [count, imageUrls])

  return {
    render,
    loaded,
  }
}

Then, render svg lazily after loading images:

// ImagePreview.tsx

import { useLoadCrossOriginImage } from './useLoadCrossOriginImage'

// This is usually state from parent component
const imageUrls = [
  'https://s3-ap-northeast-1.amazonaws.com/bucket/xxxxxxx.png',
  'https://s3-ap-northeast-1.amazonaws.com/bucket/yyyyyyy.png',
]

export const ImagePreview = () => {
  const { loaded, render } = useLoadCrossOriginImage(imageUrls)

  return (
    <div className="border border-slate-300" onClick={onClick}>
      {render()}
      {loaded && (
        <svg xmlns="http://www.w3.org/2000/svg">
          {imageUrls.map((imageUrl) => (
            <image key={el.id} href={imageUrl} />
          ))}
        </svg>
      )}
      <canvas className="hidden" />
    </div>
  )
}

Finally, you can convert the canvas element into png:

const canvas = document.querySelector('canvas')!
const ctx = canvas.getContext('2d')!
const svg = document.querySelector('svg')!
const v = Canvg.fromString(ctx, svg.outerHTML, { anonymousCrossOrigin: true })

Finally, the S3 cors policy should be like this:

{
  "CORSRules": [
    {
      "ID": "s3-cors-policy",
      "AllowedHeaders": ["*"],
      "AllowedMethods": ["GET", "HEAD"],
      "AllowedOrigins": ["*"],
      "ExposeHeaders": []
    }
  ]
}

Please leave "MaxAgeSeconds" empty.

Tallinn answered 26/7, 2022 at 9:28 Comment(0)
A
3

Here is how I solved this issue. The issue is because you cannot have an external image URL inside of your canvas. You must fetch the image and create a local copy of it. Then you can add the image and its url.

fetch("https://i.imgur.com/fHyEMsl.jpg")
  .then(result => result.blob())
  .then(blob => {
      const url = URL.createObjectURL(blob);
      // set image url
   });
Apocarpous answered 5/3, 2023 at 4:33 Comment(0)
F
2

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();
Faroff answered 26/2, 2020 at 10:2 Comment(0)
S
2

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.

Shay answered 3/5, 2021 at 7:44 Comment(0)
G
2

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

Gav answered 10/6, 2022 at 7:15 Comment(0)
T
1

I was working on react js and initially I tried the upvoted answer here. but, it didn't worked.

var img=new Image(); 
img.crossOrigin="anonymous"

Then I though to configure cors policy in server. but, before just tried one more thing.

const video = document.createElement("video")
      video.crossOrigin = "anonymous"
      video.src = url

So, adding crossOrigin both
1> to root src i.e video in my case &
2> to image src i.e preview of video in timeline (taking screenshot of video and converting it to base64)
solved the error

Tact answered 28/8, 2023 at 6:33 Comment(0)
T
0

Another case for me was to update the library itself, I was using very old alpha version. The crossorigin wasn't any effect. After the update I just used the following settings and it worked:

useCORS : true,
allowTaint : true
Technician answered 7/7, 2023 at 20:53 Comment(0)
P
0

adding crossorigin in image src string and then setting crossorigin attribute to anonymous worked for me.

const image = new Image(width, length);

image.src = `${src}?crossorigin`;

image.setAttribute('crossOrigin', 'anonymous');
Predicant answered 24/2 at 17:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.