user3465096
user3465096

Reputation: 3265

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?

Upvotes: 329

Views: 454461

Answers (18)

Shivam Rai
Shivam Rai

Reputation: 31

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');

Upvotes: 0

Vikas Acharya
Vikas Acharya

Reputation: 4152

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

Upvotes: 1

mihkov
mihkov

Reputation: 1189

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

Upvotes: 0

sknight
sknight

Reputation: 1103

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

Upvotes: 44

markE
markE

Reputation: 105035

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

Upvotes: 309

thattan
thattan

Reputation: 96

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
   });

Upvotes: 3

Kazuya  Gosho
Kazuya Gosho

Reputation: 1195

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.

Upvotes: 4

Yaser AZ
Yaser AZ

Reputation: 518

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

Upvotes: 2

Annia Martinez
Annia Martinez

Reputation: 2622

In the img tag set crossorigin to Anonymous.

<img crossorigin="anonymous" />

Upvotes: 214

Ludolfyn
Ludolfyn

Reputation: 2085

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.

Upvotes: 6

jdramer
jdramer

Reputation: 945

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"

Upvotes: 32

Jatin Mandanka
Jatin Mandanka

Reputation: 470

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();
});

Upvotes: 6

Anil Singh
Anil Singh

Reputation: 4501

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.

Upvotes: 2

BerlinaLi
BerlinaLi

Reputation: 95

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;
}

Upvotes: 6

Jeet
Jeet

Reputation: 43

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();

Upvotes: 3

mehulmpt
mehulmpt

Reputation: 16587

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

Upvotes: 47

Ahmad Zahabi
Ahmad Zahabi

Reputation: 1268

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();
    });

Upvotes: 15

Prasanna Aarthi
Prasanna Aarthi

Reputation: 3453

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

Upvotes: 12

Related Questions