6

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");
        });
};
Juncheng ZHOU
  • 115
  • 1
  • 1
  • 9

3 Answers3

12

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;
});
ɢʀᴜɴᴛ
  • 30,077
  • 15
  • 102
  • 104
1

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        | image fails to load
-+-----------------+---------------+---------------+----------------------
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't use with WebGL
-+-----------------+---------------+---------------+----------------------
6|   remote        |   not set     |     yes       | can use image in canvas 2d
 |                 |               |               | but it will dirty the canvas
 |                 |               |               | Can't use with WebGL
-+-----------------+---------------+---------------+----------------------      
7|   remote        |   set         |     no        | image fails to load
-+-----------------+---------------+---------------+----------------------      
8|   remote        |   set         |     yes       | can use image
-+-----------------+---------------+---------------+----------------------
Community
  • 1
  • 1
gman
  • 92,182
  • 29
  • 231
  • 348
  • I build a server for image also,https://newmario.herokuapp.com/img/grassMid.png, but it's not working – Juncheng ZHOU Apr 09 '17 at 19:13
  • I modify the header: var allowCrossDomain = function(req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); res.header('Access-Control-Allow-Headers', '*'); next(); } – Juncheng ZHOU Apr 09 '17 at 19:17
  • yeah, but why it's not working for me? you can check the response header of the image. you can see : Access-Control-Allow-Headers:* – Juncheng ZHOU Apr 09 '17 at 19:29
  • show your image loading code, also remove this line `this.image.crossOrigin = '*';`. It makes no sense to set crossOrigin for a local dataURL – gman Apr 09 '17 at 19:32
0

set useCORS to ture can solve this issue

html2Canvas(document.querySelector('#pdfDom'), {useCORS: true}).then((canvas) => 
{
    let pageData = canvas.toDataURL('image/jpeg', 1.0);
}
Pradeep
  • 9,481
  • 13
  • 27
  • 34
jincy
  • 1