web-dev-qa-db-fra.com

Convertir SVG intégré en PNG sur place

Je génère du HTML à partir d'une source Docbook tout en utilisant SVG pour les images (converties à partir de MathML). Cela fonctionne bien pour certains navigateurs qui peuvent interpréter SVG, mais échoue pour d'autres.

Ce que j'aimerais vraiment, c'est ajouter une étape de post-traitement qui convertira SVG en PNG "sur place" (dans le HTML).

Donc quelque chose comme ça:

<body>
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
        <circle cx="50" cy="50" r="30" />
    </svg>
</body>

Serait parfaitement converti en ceci:

<body>
    <img src="img0001.png" />
</body>

Et j'obtiendrais un PNG converti à côté.

Y a-t-il quelque chose qui fera ça?

31
jjkparker

Démo: http://phrogz.net/SVG/svg_to_png.xhtml

  1. Créez un img et définissez son src sur votre SVG.
  2. Créez un canevas HTML5 et utilisez drawImage() pour dessiner cette image sur votre canevas.
  3. Utilisez toDataURL() sur le canevas pour obtenir un PNG encodé en base64.
  4. Créez un img et définissez son src sur cette URL de données.
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 );

Cependant, cette réponse (et toutes les solutions côté client uniquement) nécessite que le navigateur prenne en charge SVG, ce qui peut le rendre inutile pour vos besoins spécifiques.

Modifier : Cette réponse suppose que le SVG est disponible en tant qu'URL distincte. En raison des problèmes décrits dans cette question Je ne peux pas faire fonctionner ce qui précède avec un document SVG incorporé dans le même document effectuant le travail.

Edit 2 : Les problèmes décrits dans cette autre question ont été résolus par des améliorations de Chrome et Firefox. Il y a toujours la limitation que le <svg> l'élément doit avoir width="…" height="…" des attributs pour Firefox pour lui permettre d'être dessiné sur un canevas. Et Safari entache actuellement la toile entière chaque fois que vous y dessinez un SVG (quelle que soit la source) mais cela devrait bientôt changer .

42
Phrogz

J'ai rencontré ce problème le week-end dernier et j'ai fini par écrire une simple bibliothèque pour faire plus ou moins ce que Phrogz décrit. Il code également en dur le style du SVG cible afin d'éviter les problèmes de rendu. J'espère que la prochaine personne qui cherche un moyen de le faire peut simplement utiliser mon code et passer aux défis les plus intéressants!

P.S. Je n'ai testé cela que dans 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);
}
4
user3229542

FOP et Batik

http://xmlgraphics.Apache.org/fop/

http://xmlgraphics.Apache.org/batik/

FOP, d'Apache, incorpore Batik, également d'Apache. Batik dispose d'un outil de rendu SVG qui générera vos PNG. FOP est également un outil de génération de documents.

3
Josh Pearce

J'ai pris le code @Phrogz ci-dessus et fait un extrait de travail. Pas certain mySVG.clientWidth fonctionne cependant dans FF. Il est également disponible ici - 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==" />

[~ # ~] mise à jour [~ # ~] : Refactorisé un peu - https://jsfiddle.net/e4r8sk18/1/

UPDATE2 : Refactorisé en classe de convertisseur - https://jsfiddle.net/07a93Lt6/5/

3

c'est assez ancien, mais j'ai trouvé un extrait plus simple qui fait le travail correctement dans les navigateurs modernes: https://Gist.github.com/Caged/4649511

1
Ramiro Araujo

si vous voulez le faire uniquement du côté client, vous aurez besoin de deux étapes:

  1. convertir SVG en toile (http://code.google.com/p/canvas-svg/ ou d'autres outils)
  2. convertir Canvas en PNG (http://www.nihilogic.dk/labs/canvas2image/ ou d'autres outils)

cela ne fonctionnera évidemment que dans les navigateurs compatibles HTML5.

1
wildcard