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