开发者

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

  1. Create an img and set its src to your SVG.
  2. Create an HTML5 canvas and use drawImage() to draw that image to your canvas.
  3. Use toDataURL() on the canvas to get a base64 encoded PNG.
  4. 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:

  1. convert SVG to Canvas (http://code.google.com/p/canvas-svg/ or some other tools)
  2. convert Canvas to PNG (http://www.nihilogic.dk/labs/canvas2image/ or some other tools)

this obviously will work only in HTML5-capable browsers.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜