Convert embedded SVG to PNG in-place
I'm generating HTML from a Docbook source while using SVG for images (converted from MathML). This works fine for some browsers that can interpret SVG, but fails for others.
What I would really like is to add a post-processing step that will convert SVG to PNG "in-place" (within the HTML).
So something like this:
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="50" cy="50" r="30" />
</svg>
</body>
Would get seamlessly converted to this:
<body>
<img src="img0001.png" />
</body>
And I would get a开发者_如何学Go converted PNG alongside.
Is there something that will do this?
Demo: http://phrogz.net/SVG/svg_to_png.xhtml
- Create an
img
and set its src to your SVG. - Create an HTML5 canvas and use
drawImage()
to draw that image to your canvas. - Use
toDataURL()
on the canvas to get a base64 encoded PNG. - Create an img and set it's src to that data URL.
var mySVG = document.querySelector('…'), // Inline SVG element
tgtImage = document.querySelector('…'), // Where to draw the result
can = document.createElement('canvas'), // Not shown on page
ctx = can.getContext('2d'),
loader = new Image; // Not shown on page
loader.width = can.width = tgtImage.width;
loader.height = can.height = tgtImage.height;
loader.onload = function(){
ctx.drawImage( loader, 0, 0, loader.width, loader.height );
tgtImage.src = can.toDataURL();
};
var svgAsXML = (new XMLSerializer).serializeToString( mySVG );
loader.src = 'data:image/svg+xml,' + encodeURIComponent( svgAsXML );
However, this answer (and all client-side only solutions) require the browser to support SVG, which may make it useless for your specific needs.
Edit: This answer assumes that the SVG is available as a separate URL. Due to the problems described in this question I cannot get the above to work with an SVG document embedded in the same document performing the work.
Edit 2: The problems described in that other question have been overcome by improvements to Chrome and Firefox. There is still the limitation that the <svg>
element must have width="…" height="…"
attributes for Firefox to allow it to be drawn to a canvas. And Safari currently taints the entire canvas whenever you draw any SVG to it (regardless of source) but that should change soon.
I ran into this problem over the past weekend, and ended up writing a simple library to do more or less what Phrogz describes. It also hard codes the target SVG's style in order to avoid rendering issues. Hopefully the next person looking for a way to do this can simply use my code and move on to the more interesting challenges!
P.S. I only tested this in Chrome.
// Takes an SVG element as target
function svg_to_png_data(target) {
var ctx, mycanvas, svg_data, img, child;
// Flatten CSS styles into the SVG
for (i = 0; i < target.childNodes.length; i++) {
child = target.childNodes[i];
var cssStyle = window.getComputedStyle(child);
if(cssStyle){
child.style.cssText = cssStyle.cssText;
}
}
// Construct an SVG image
svg_data = '<svg xmlns="http://www.w3.org/2000/svg" width="' + target.offsetWidth +
'" height="' + target.offsetHeight + '">' + target.innerHTML + '</svg>';
img = new Image();
img.src = "data:image/svg+xml," + encodeURIComponent(svg_data);
// Draw the SVG image to a canvas
mycanvas = document.createElement('canvas');
mycanvas.width = target.offsetWidth;
mycanvas.height = target.offsetHeight;
ctx = mycanvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Return the canvas's data
return mycanvas.toDataURL("image/png");
}
// Takes an SVG element as target
function svg_to_png_replace(target) {
var data, img;
data = svg_to_png_data(target);
img = new Image();
img.src = data;
target.parentNode.replaceChild(img, target);
}
I took @Phrogz code above and made a working snippet. Not sure mySVG.clientWidth
works in FF though. It also available here - https://jsfiddle.net/LLjLpo05/
var mySVG = document.querySelector('#svblock'), // Inline SVG element
tgtImage = document.querySelector('#diagram_png'), // Where to draw the result
can = document.createElement('canvas'), // Not shown on page
ctx = can.getContext('2d'),
loader = new Image; // Not shown on page
//loader.width = can.width = tgtImage.width = mySVG.getBBox().width;
//loader.height = can.height = tgtImage.height = mySVG.getBBox().height;
loader.width = can.width = tgtImage.width = mySVG.clientWidth;
loader.height = can.height = tgtImage.height = mySVG.clientHeight;
loader.onload = function() {
ctx.drawImage(loader, 0, 0, loader.width, loader.height);
tgtImage.src = can.toDataURL();
};
var svgAsXML = (new XMLSerializer).serializeToString(mySVG);
loader.src = 'data:image/svg+xml,' + encodeURIComponent(svgAsXML);
<div id="diagram_image">
<svg id="svblock" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 640 120">
<defs id="defs_block">
<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" />
</filter>
</defs>
<title>blockdiag</title>
<desc/>
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="46" />
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="46" />
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="451" y="46" />
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="40" />
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="128" y="66">discovery</text>
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="40" />
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="320" y="66">execution</text>
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="448" y="40" />
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="512" y="66">reporting</text>
<path d="M 192 60 L 248 60" fill="none" stroke="rgb(0,0,0)" />
<polygon fill="rgb(0,0,0)" points="255,60 248,56 248,64 255,60" stroke="rgb(0,0,0)" />
<path d="M 384 60 L 440 60" fill="none" stroke="rgb(0,0,0)" />
<polygon fill="rgb(0,0,0)" points="447,60 440,56 440,64 447,60" stroke="rgb(0,0,0)" />
</svg>
</div>
<img id="diagram_png" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" />
UPDATE: Refactored a bit - https://jsfiddle.net/e4r8sk18/1/
UPDATE2: Refactored into converter class - https://jsfiddle.net/07a93Lt6/5/
FOP and Batik
http://xmlgraphics.apache.org/fop/
http://xmlgraphics.apache.org/batik/
FOP, from Apache, incorporates Batik, also from Apache. Batik has an SVG rendering tool which will generate your PNGs. FOP also is a document generating tool.
this is rather old, but I found a simpler snippet that does the job correctly in modern browsers: https://gist.github.com/Caged/4649511
if you want to do it purely on the client-side, you would need two steps:
- convert SVG to Canvas (http://code.google.com/p/canvas-svg/ or some other tools)
- convert Canvas to PNG (http://www.nihilogic.dk/labs/canvas2image/ or some other tools)
this obviously will work only in HTML5-capable browsers.
精彩评论