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

13

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;
});
ɢʀᴜɴᴛ
  • 32,025
  • 15
  • 116
  • 110
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        | 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
-+-----------------+---------------+---------------+----------------------
gman
  • 100,619
  • 31
  • 269
  • 393
  • 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
  • "*You have to set img.crossOrigin BEFORE you set img.src. [...] The request is sent the moment you set img.src.*". Nope, the actual request is made in a microtask that's queued when setting the src, to allow setting `crossOrigin` later, or even changing the `src` later and not have many useless requests (e.g for cases like `if(x) img.src += something`) – Kaiido Aug 13 '23 at 01:43
  • That's what the spec says but it's not what browsers do. Older browsers didn't have this behavior. Just tested Chrome v18 and it definitely required setting the `crossOrigin` attribute before setting `src`. Modern Firefox 115.0.3 (Win64) also fails. It loads the image when `src` is set, fires a load event, reloads the image when `crossOrigin` is set, fires a second load event when debugging. – gman Aug 14 '23 at 03:55
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,667
  • 13
  • 27
  • 34
jincy
  • 1