Convert embedded SVG to PNG in-place

后端 未结 6 548
自闭症患者
自闭症患者 2020-11-29 17:20

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

相关标签:
6条回答
  • 2020-11-29 17:58

    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);
    }
    
    0 讨论(0)
  • 2020-11-29 18:06

    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/

    0 讨论(0)
  • 2020-11-29 18:14

    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.

    0 讨论(0)
  • 2020-11-29 18:15

    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 讨论(0)
  • 2020-11-29 18:16

    this is rather old, but I found a simpler snippet that does the job correctly in modern browsers: https://gist.github.com/Caged/4649511

    0 讨论(0)
  • 2020-11-29 18:19

    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.

    0 讨论(0)
提交回复
热议问题