Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
266 views
in Technique[技术] by (71.8m points)

javascript - Problems with getting canvas dataURI from svg with foreignObject

Hi I have the following problem.

I'm generating a SVG image (https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Drawing_DOM_objects_into_a_canvas).

The image is generated correctly and looks ok. Now I need to get the data URI, but everytime I try to get that from canvas.toDataURL() this message appears Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.(…)

I've created some sample code to illustrate the situation.

</!DOCTYPE html>
<html>
<head>
    <title>SVG to PNG</title>
</head>
<body>
    <canvas id="canvas" style="border:2px solid black;" width="200" height="200">
</canvas>


<script type="text/javascript">

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
               '<foreignObject width="100%" height="100%">' +
               '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
                 '<em>I</em> like ' + 
                 '<span style="color:white; text-shadow:0 0 2px blue;">' +
                 'beer</span>' +
               '</div>' +
               '</foreignObject>' +
            '</svg>';

var domURL = window.URL || window.webkitURL || window;

var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml'});
var url = domURL.createObjectURL(svg);

img.onload = function () {
  ctx.drawImage(img, 0, 0);
  domURL.revokeObjectURL(url);
  var dataURL = canvas.toDataURL();
  console.log(dataURL);
};

img.src = url;

</script>
</body>
</html>

This code generates the following image

enter image description here

This is correct, but then when these lines are executed

var dataURL = canvas.toDataURL(); console.log(dataURL);

It throws the error. I'm doing this inside the image onload method to allow the image finish drawing to the canvas. If I try to get the dataURL from outside the onload method, the canvas hasn't finished loading, so it would give me an empty image. I've been searching a lot but I couldn't find the answer yet. This is something related to CORS. I've installed the chrome plugin CORS and added the img, the thing is that this is just an example, and a CORS based solution would not be useful, because I'm working on an application that's running over a crippled chromium browser (I mean the browser just shows the web app, you can't do anything there except interact with the app).

To notice you can obtain the data URI, with

inspect --> network --> select the image and open in sources panel and there it is just copy image as data uri. I get the base64 image that way, and if I use some decoder like this (http://base64online.org/decode/) it's showing the image correctly. So definitely the problem is that I'm getting the data URI before the canvas is drawn.

Any ideas?

Thanks in advance!

Regards

Mauro

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This has nothing to do with CORS. You are not doing any request to an external resource.

The issue is that drawing an svg (moreover when it contains html elements) is a really sensitive action for browsers.

That's why there are a lot of limitations on this technique (you can't load external resources, scripts are ignored, you can't style the content from external CSS, all default browser and OS styles are disabled to avoid fingerprinting etc.).
I don't work for safari nor chrome, so I can't tell for sure, but I think they aren't able to provide enough security on one of these points (safari realised it in v9, and it's a known issue in chrome too).

So what they do, is that they taint the canvas, locking any of its export methods. (Note that IE < Edge did also taint the canvas whenever any svg had been drawn to the canvas for similar security reasons).

If what you want is to rasterize DOM elements, then you should parse the DOM and all its applied styles, and reproduce it with canvas drawings methods.
Doing so, you can bypass most security issues, and no UA will taint the canvas. Some library out there do exactly this, the most famous being html2canvas.

If what you want is to draw this image, then you can rewrite it without using a foreignObject (svg text has more options than canvas one), then use a library like canvg to render it on your canvas (because otherwise IE will taint the canvas as said previously).


Note : chrome's issue can be workedaround, but I'm not sure it's a good idea, it will still not work in Safari, nor in IE < Edge, and chrome may just have forgotten to block it too and will in next releases, anyway, here is how :

var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');

var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
  '<foreignObject width="100%" height="100%">' +
  '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
  '<em>I</em> like ' +
  '<span style="color:white; text-shadow:0 0 2px blue;">' +
  'beer</span>' +
  '</div>' +
  '</foreignObject>' +
  '</svg>';


var img = new Image();
// instead of a blobURL, if we use a dataURL, chrome seems happy...
var url = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(data);

img.onload = function() {
  c.width = this.width;
  c.height = this.height;
  ctx.drawImage(img, 0, 0);
  var dataURL = canvas.toDataURL();
  console.log(dataURL);
};

img.src = url;
<canvas id="c"></canvas>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...