Juncheng ZHOU
Juncheng ZHOU

Reputation: 125

Failto execute toDataURL on HTMLCanvasElement

After I draw the canvas from an image(local file), I try to export it with the command ctx.canvas.toDataURL("image/png")

But there is an error:

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

I already searched in google. They said this is the problem of Cross. So, I added command:

image.crossOrigin = '*';

But this is useless for my project. Actually, my project is building on local without any server. So, I have no idea why there is the problem of cross-domain.

function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
    var image = new Image();
    image.onload = function() {
        image.crossOrigin = '*';
        resolve(image);
    };
    image.onerror = function() {
        reject(new Error('Could not load image at ' + url));
    };
    image.src = url;
});


  generate() {
    var p1 = loadImageAsync(this.textures[1]);
    var p2 = loadImageAsync(this.textures[2]);
    var p3 = loadImageAsync(this.textures[3]);
    var ctx = document.createElement("canvas")
        .getContext("2d");
    ctx.canvas.width = this.width;
    ctx.canvas.height = this.height;
    var rows = ~~(this.width / 70);
    var cols = ~~(this.height / 70);
    Promise.all([p1, p2, p3])
        .then(imgs => {
            for (let x = 0, i = 0; i < rows; x += 70, i++) {
                for (let y = 630, j = 0; j < cols; y -= 70, j++) {
                    this.resource[i].forEach(item => {
                        switch (item) {
                            case 1:
                                ctx.drawImage(imgs[0], x, y, 70, 70);
                                break;
                            case 2:
                                ctx.drawImage(imgs[1], x, y, 70, 70);
                                break;
                            case 3:
                                ctx.drawImage(imgs[2], x, y, 70, 70);
                                break;
                            default:
                        }
                    });
                }
            }
            //window.ctx = ctx;
            this.image.crossOrigin = '*';
            this.image.src = ctx.canvas.toDataURL("image/png");
        });
};

Upvotes: 6

Views: 28853

Answers (3)

user128511
user128511

Reputation:

When you load images, if they are from another domain they will be marked as cross domain unless they have CORS permissions. This includes loading files from file://. Cross domain images without CORS permissions will taint the canvas if using canvas 2d, and are not usable at all if using WebGL

If the files are local it's best to use a simple server. Here's one and here's a bunch more

If the images are actually cross domain then you need to request CORS permission by setting img.crossOrigin and the server needs to return the correct headers for the images. I believe the only header needed is

 Access-Control-Allow-Origin: *

You have to set img.crossOrigin BEFORE you set img.src. Setting img.crossOrigin tells the browser to request the permission from the server. The request is sent the moment you set img.src.

Let's try it with an imgur URL which I happen to know supports CORS, also your URL you mentioned, and one from my site which I know does NOT support CORS

[
  { url: "https://i.imgur.com/TSiyiJv.jpg", crossOrigin: "*", },
  { url: "https://newmario.herokuapp.com/img/grassMid.png", crossOrigin: "*", },
  { url: "https://greggman.com/images/echidna.jpg",  /* NO CORS */ },
].forEach(loadImage);

function loadImage(info) {
  const url = info.url;
  const img = new Image()
  img.onload = function() {
     const ctx = document.createElement("canvas").getContext("2d");
     try {
       ctx.drawImage(img, 0, 0);
       ctx.canvas.toDataURL();
       log("got CORS permission for:", url);
     } catch(e) {
       log("**NO** CORS permission for:", url);
     }
  }
  img.onerror = function() {
    log("could not load image:", url);
  }
  img.crossOrigin = info.crossOrigin;
  img.src = url;
}

function log(...args) {
  const elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}
pre { margin: 0; }

My results, the imgur image works, yours works, mine does not (as expected)

Note that there are 8 cases

 |   local/remote  |  crossOrigin  |  CORS headers |  Result
-+-----------------+---------------+---------------+---------------------
1|   local         |   not set     |     no        | can use image
-+-----------------+---------------+---------------+----------------------
2|   local         |   not set     |     yes       | can use image
-+-----------------+---------------+---------------+----------------------
3|   local         |   set         |     no        | can use image
-+-----------------+---------------+---------------+----------------------
4|   local         |   set         |     yes       | can use image
-+-----------------+---------------+---------------+----------------------
5|   remote        |   not set     |     no        | can use image in canvas 2d
 |                 |               |               | but it will dirty the canvas.
 |                 |               |               | Can not use with WebGL
-+-----------------+---------------+---------------+----------------------
6|   remote        |   not set     |     yes       | can use image in canvas 2d
 |                 |               |               | but it will dirty the canvas
 |                 |               |               | Can not use with WebGL
-+-----------------+---------------+---------------+----------------------      
7|   remote        |   set         |     no        | image fails to load
-+-----------------+---------------+---------------+----------------------      
8|   remote        |   set         |     yes       | can use image
-+-----------------+---------------+---------------+----------------------

Upvotes: 1

jincy
jincy

Reputation: 1

set useCORS to ture can solve this issue

html2Canvas(document.querySelector('#pdfDom'), {useCORS: true}).then((canvas) => 
{
    let pageData = canvas.toDataURL('image/jpeg', 1.0);
}

Upvotes: 0

ɢʀᴜɴᴛ
ɢʀᴜɴᴛ

Reputation: 32879

You need to set crossOrigin for the image outside the onload function.

return new Promise(function(resolve, reject) {
    var image = new Image();
    image.crossOrigin = '*';  //<-- set here
    image.onload = function() {
        resolve(image);
    };
    image.onerror = function() {
        reject(new Error('Could not load image at ' + url));
    };
    image.src = url;
});

Upvotes: 14

Related Questions