Преобразование встроенных SVG в PNG на месте

Я генерирую HTML из источника Docbook при использовании SVG для изображений (преобразованных из MathML). Это отлично подходит для некоторых браузеров, которые могут интерпретировать SVG, но не для других.

Мне бы очень хотелось добавить шаг пост-обработки, который преобразует SVG в PNG "на месте" (внутри HTML).

Так что-то вроде этого:

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

Будет легко преобразован в это:

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

И я бы получил преобразованный PNG рядом.

Есть ли что-то, что сделают это?

Ответ 1

Демо: http://phrogz.net/SVG/svg_to_png.xhtml

  • Создайте img и установите его src в свой SVG.
  • Создайте холст HTML5 и используйте drawImage(), чтобы нарисовать это изображение на вашем холсте.
  • Используйте toDataURL() на холсте, чтобы получить PNG с кодировкой base64.
  • Создайте img и установите его src в этот 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  = myTargetImage.width;
loader.height = can.height = myTargetImage.height;
loader.onload = function(){
  ctx.drawImage( loader, 0, 0, loader.width, loader.height );
  tgtImage.src = can.toDataURL();
};
var svgAsXML = (new XMLSerializer).serializeToString( mySVGElement );
loader.src = 'data:image/svg+xml,' + encodeURIComponent( svgAsXML );

Однако этот ответ (и все решения только на стороне клиента) требует, чтобы браузер поддерживал SVG, что может сделать его бесполезным для ваших конкретных потребностей.

Изменить. Этот ответ предполагает, что SVG доступен в виде отдельного URL-адреса. Из-за проблем, описанных в этом вопросе, я не могу заставить выше работать с SVG-документом, встроенным в тот же документ, выполняющий работу.

Изменить 2. Проблемы, описанные в этом другом вопросе, были устранены усовершенствованиями Chrome и Firefox. По-прежнему существует ограничение на то, что элемент <svg> должен иметь атрибуты width="…" height="…" для Firefox, чтобы он можно было нарисовать на холсте. И Safari в настоящее время загрязняет весь холст всякий раз, когда вы рисуете любой SVG (независимо от источника), но который должен скоро измениться.

Ответ 2

Я столкнулся с этой проблемой в прошлые выходные и в итоге написал простую библиотеку, чтобы сделать более или менее то, что описывает Фрогз. Он также жестко кодирует целевой стиль SVG, чтобы избежать проблем с воспроизведением. Надеюсь, следующий человек, который ищет способ сделать это, может просто использовать мой код и перейти к более интересным задачам!

P.S. Я тестировал это только в 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 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);
}

Ответ 3

FOP и Батик

http://xmlgraphics.apache.org/fop/

http://xmlgraphics.apache.org/batik/

FOP, из Apache, включает Batik, также из Apache. У Batik есть инструмент рендеринга SVG, который будет генерировать ваши PNG. FOP также является инструментом создания документа.

Ответ 4

Я взял код @Phrogz выше и сделал рабочий фрагмент. Не уверен, что mySVG.clientWidth работает в FF. Он также доступен здесь - 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==" />

Ответ 5

если вы хотите сделать это исключительно на стороне клиента, вам понадобятся два шага:

  • конвертировать SVG в Canvas (http://code.google.com/p/canvas-svg/или некоторые другие инструменты)
  • конвертировать Canvas в PNG (http://www.nihilogic.dk/labs/canvas2image/или некоторые другие инструменты)

это, очевидно, будет работать только в браузерах, поддерживающих HTML5.

Ответ 6

Это довольно старый, но я нашел более простой фрагмент, который корректно выполняет работу в современных браузерах: https://gist.github.com/Caged/4649511