1

I'm trying to make a button that, when pressed, downloads a canvas element as a PNG file.

function downloadImage(canvas)
{
    let url = canvas.toDataURL("image/png");
    window.open(url);
}

This code works for untainted canvases, but the canvas I'm trying to download an image of is already tainted so I cannot run .toDataURL() on it. Many threads I've seen dealing with tainted canvases are related to creating image displays of the canvas, but is there any way to directly download it without creating an intermediate image element?

My site is very simple and I'm not running it on any servers at the moment (just opening the index.html file in Google Chrome). However, I also don't want to be changing any local browser settings because I want it to run on multiple different browsers.

sw2037122
  • 63
  • 6
  • so you're doing everything using the `file:///` protocol? no external images from other sites? use firefox, it doesn't consider `file:///` to be cross origin – Jaromanda X May 18 '20 at 02:07
  • I'm working within the file:/// environment when I'm debugging, but eventually I do want to publish it to Github Pages where people from different browsers will be accessing it, so changing browsers isn't exactly a solution for me. – sw2037122 May 18 '20 at 02:11
  • right, so in github pages will the images be same origin? – Jaromanda X May 18 '20 at 02:13
  • Images are uploaded directly to the website and not stored anywhere, and the canvas is made from manipulating the image, so I think the answer is yes. – sw2037122 May 18 '20 at 02:16
  • 1
    then you won't have a problem once you get away from `file:///` protocol - use a local http server for development - very simple with nodejs or python – Jaromanda X May 18 '20 at 02:25
  • Yup, it worked as soon as I pushed the site onto github.io. Thank you so much! I wanted a way to download the canvas within the file:/// environment, but since I was going to publish it anyway, I guess it doesn't make a difference. Thank you to everyone else who who took the time to respond. I should've been more considerate to post an update sooner. – sw2037122 May 18 '20 at 03:27

1 Answers1

0

The problem is that JavaScript is designed to make you not download any tainted image.

However, this can be circumvented. You mentioned in the question that you are using the file protocol; however, it was mentioned in the comments that you would be open to using a web server at github pages ultimately, so perhaps, if we work in that context, we could get this done.

First off, again, to directly answer your question, how to download a tainted canvas that has been manipulated, officially the browser is designed in a way that's meant to prevent that from happening, see https://stackoverflow.com/a/36905778/2016831 for more info on that.

So now for the possible workarounds.

It depends why the canvas is tainted, was something drawn on it beforoe your code has access to it, or is your code making it tainted in the first place?

If its the second option, then, as mentioned in the answer quoted, you could do a couple of things.

First, try adding, on the image element, in JavaScript, the attribute crossOrigin = "anonymous", which may work for some sites.

For others though, you could set up a simple server using nodeJS or PHP or any other server side language, hosted pretty on any hosting platform which would make an HTTP request to the image URL given, download it, and serve it back to your client file, with access-control-allow-origin set to *, but, as mentioned in the answer also, that would take up more bandwidth, but if bandwidth isn't an issue, then you could do that.

Alternatively, of course, you could have the user simply upload the image via a file input instead of a URL, but that would limit usability.

Another alternative, related to the first "crossOrigin" solution mentioned, is, if the image comes from a site which allows cross-origin requests, you can first download it with fetch, then make an object URL from the blob data, and set the source of the image to that, then when the image loads, draw it to the canvas, basically

fetch("someURLtoAnImageOnaSiteThatAllowsCrossOriginRequests")
.then(result => result.blob())
.then(blob => {
    myImageElement.src = URL.createObjectURL(blob);
    myImageElement.onload = startDrawingImageToCanvasFunction;
})