Search code examples
javascriptsvghtml2canvas

html2canvas download image with background image on SVG


Hi html2canvas & SVG masters maybe you can help me, im trying to download an image from a SVG where the user sets images on the SVG areas, but when i generate the image with html2canvas the background images appears strange and to download the generated file, i get an error: "Uncaught (in promise) DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported."

I made an example to better understanding: http://jsfiddle.net/equerol/sodofkcs/5711/

  html2canvas(document.getElementById("svgContainer"), {
    logging: true,
    allowTaint: true,
  }).then(function(canvas) {
    document.body.appendChild(canvas);
    var myImage = canvas.toDataURL("image/png");
    downloadURI("data:" + myImage, "yourImage.png");
  });

Thanks.


Solution

  • I don't really understand your question. I think you're asking two things here (Images appear strange, download is not possible due to tainted canvas).

    For the first question you should clarify what you're really asking (What does strange mean? what do you want to achieve?...)

    For the second (Tainted canvas), you are dealing with a security problem:

    Allowing cross-origin use of images and canvas

    The above link explains in depth the problem you are facing.

    You should try to use one of the approaches suggested in the link.

    I can easily overcome the security problem by using an embedded image data URI within your canvas so that it's no longer tainted. I basically replaced your external image URL with a dataURI. Please try to find a better solution that suits your problem.

    Working version of your fiddle (download canvas is possible)

    Here is the affected code and what I replaced to make it work (truncated):

    /* ... */
                <g id="svgPoly4" clip-path="url(#d)" class="droppable">
                  <image width="500" height="500" xlink:href="data:image/png;base64,iVBORw0KGgoAAAA....ggg=="></image>
            </g>
    /* ... */
    

    Update 1 // 2019-01-08

    Sorry, in the original answer I should have been more clear about the security issue you are facing.

    Basically you are trying to get a screenshot of a foreign image reformatted/redesigned within an SVG composition. This means that a malicious entity could use this technique to hijack sensitive information (images) from a user in case the browser didn't prevent it.

    What you should really do to overcome your problem is serve the images embedded in the SVG from the same origin as the rest of the page. If for some reason/use case you require to fetch foreign images for the composition, these images should be proxied by your back-end.

    Anyway, I've tweaked a little more your fiddle and now it's usable from Chrome, Firefox and Safari. Edge won't work (but given your initial implementation I guess you haven't tried it yet).

    Upgraded jsFiddle version

    This version highlights what you should do to replace the image in your SVG.

    Note: The image has been previously uploaded to a server that allows cross origin access to resources (it adds Access-Control-Allow-Origin header)

    The SVG html is loaded as a template string where the data URL will be replaced when the image is loaded.

    Image is loaded:

    var downloadedImg = new Image;
    downloadedImg.crossOrigin = "Anonymous";
    downloadedImg.addEventListener("load", imageReceived, false);
    downloadedImg.src = "https://i.imgur.com/szhc74z.png";
    

    Data URL is replaced in template SVG html string:

    var svg = svgTemplate.replace('${imageUrl}', dataUrl);
    document.getElementById('rootSvg').innerHTML = svg;
    

    Additional workaround for Safari:

    It appears that there's some kind of issue with html2canvas and the way it "re-renders" the SVG.

    To mitigate this, html2canvas function is invoked once the SVG is added to the document. This will "warm up" the procedure for the "real" invocations.

    The intention of my fiddle is just to demonstrate that the feat can be accomplished, but please try to use the back-end proxy approach or a different design to solve your problem.