Como faço para converter um svg
para png
, em Python? Estou armazenando svg
em uma instância de StringIO
. Devo usar a biblioteca pyCairo? Como faço para escrever esse código?
Como faço para converter um svg
para png
, em Python? Estou armazenando svg
em uma instância de StringIO
. Devo usar a biblioteca pyCairo? Como faço para escrever esse código?
Respostas:
A resposta é " pyrsvg " - uma ligação Python para librsvg .
Existe um pacote Ubuntu python-rsvg que o fornece. Pesquisar seu nome no Google é ruim porque seu código-fonte parece estar contido no repositório GIT do projeto Gnome "gnome-python-desktop".
Fiz um "hello world" minimalista que renderiza SVG em uma superfície do Cairo e grava em disco:
import cairo
import rsvg
img = cairo.ImageSurface(cairo.FORMAT_ARGB32, 640,480)
ctx = cairo.Context(img)
## handle = rsvg.Handle(<svg filename>)
# or, for in memory SVG data:
handle= rsvg.Handle(None, str(<svg data>))
handle.render_cairo(ctx)
img.write_to_png("svg.png")
Atualização : a partir de 2014 o pacote necessário para a distribuição Fedora Linux é: gnome-python2-rsvg
. A lista de trechos acima ainda funciona como está.
cairo
determinar a ALTURA e a LARGURA da imagem por conta própria? Verifiquei o *.svg
arquivo para extrair ALTURA e LARGURA de lá, mas ambos estão configurados para 100%
. Claro, posso examinar as propriedades da imagem, mas, como essa é apenas uma etapa do processamento da imagem, não é isso que eu quero.
.get_dimension_data()
método que funcionou para o meu arquivo de exemplo (um SVG bem comportado) - experimente.
Aqui está o que eu fiz usando cairosvg :
from cairosvg import svg2png
svg_code = """
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="8" x2="12" y2="12"/>
<line x1="12" y1="16" x2="12" y2="16"/>
</svg>
"""
svg2png(bytestring=svg_code,write_to='output.png')
E funciona como um encanto!
Veja mais: documento cairosvg
svg2png
leva um stream
objeto no write_to
parâmetro, e este pode ser seu objeto de resposta HTTP (que em a maioria das estruturas é um objeto semelhante a um arquivo) ou algum outro fluxo, que você serve ao navegador usando o Content-Disposition
cabeçalho. veja aqui: stackoverflow.com/questions/1012437/…
bytestring
aceita bytes, portanto, converta primeiro a string com bytestring=bytes(svg,'UTF-8')
2). o modo de arquivo deve ser binário, entãoopen('output.png','wb')
svg2png
para mim, eu tinha que usar cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png')
.
Instale o Inkscape e chame-o como linha de comando:
${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -j -e ${dest_png}
Você também pode capturar uma área retangular específica usando apenas o parâmetro -j
, por exemplo, coordenada "0: 125: 451: 217"
${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -j -a ${coordinates} -e ${dest_png}
Se quiser mostrar apenas um objeto no arquivo SVG, você pode especificar o parâmetro -i
com o id do objeto que você configurou no SVG. Ele esconde todo o resto.
${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -i ${object} -j -a ${coordinates} -e ${dest_png}
Estou usando o Wand-py (uma implementação do wrapper Wand em torno do ImageMagick) para importar alguns SVGs bastante avançados e até agora tenho visto ótimos resultados! Este é todo o código necessário:
with wand.image.Image( blob=svg_file.read(), format="svg" ) as image:
png_image = image.make_blob("png")
Acabei de descobrir isso hoje e senti que valia a pena compartilhar com qualquer pessoa que possa ter dúvidas sobre essa resposta, pois já faz um tempo que a maioria dessas perguntas não foi respondida.
NOTA: Tecnicamente em testes, descobri que você nem mesmo precisa passar o parâmetro de formato para ImageMagick, então with wand.image.Image( blob=svg_file.read() ) as image:
foi tudo o que realmente foi necessário.
EDIT: A partir de uma tentativa de edição por qris, aqui está um código útil que permite usar o ImageMagick com um SVG que tem um fundo transparente:
from wand.api import library
import wand.color
import wand.image
with wand.image.Image() as image:
with wand.color.Color('transparent') as background_color:
library.MagickSetBackgroundColor(image.wand,
background_color.resource)
image.read(blob=svg_file.read(), format="svg")
png_image = image.make_blob("png32")
with open(output_filename, "wb") as out:
out.write(png_image)
image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
svg_file
é considerado um objeto "arquivo" neste exemplo, a configuração svg_file
seria algo como:svg_file = File.open(file_name, "r")
cairo
e rsvg
'aceito' não funcionou para o meu PDF. pip install wand
e seu snippet funcionou;)
str
, em seguida, você primeiro precisa codificar em binário da seguinte forma: svg_blob = svg_str.encode('utf-8')
. Agora você pode usar o método acima, substituindo blob=svg_file.read()
por blob=svg_blob
.
Experimente: http://cairosvg.org/
O site diz:
CairoSVG é escrito em python puro e depende apenas do Pycairo. É conhecido por funcionar em Python 2.6 e 2.7.
Atualização em 25 de novembro de 2016 :
2.0.0 é uma nova versão principal, seu changelog inclui:
- Abandone o suporte Python 2
<clipPath><rect ... /></clipPath>
. Em segundo lugar, não é necessária a opção -d (DPI).
Outra solução que acabei de encontrar aqui Como renderizar um SVG em escala para um QImage?
from PySide.QtSvg import *
from PySide.QtGui import *
def convertSvgToPng(svgFilepath,pngFilepath,width):
r=QSvgRenderer(svgFilepath)
height=r.defaultSize().height()*width/r.defaultSize().width()
i=QImage(width,height,QImage.Format_ARGB32)
p=QPainter(i)
r.render(p)
i.save(pngFilepath)
p.end()
PySide é facilmente instalado a partir de um pacote binário no Windows (e eu o uso para outras coisas, então é fácil para mim).
No entanto, notei alguns problemas ao converter bandeiras de países da Wikimedia, então talvez não seja o analisador / renderizador svg mais robusto.
Uma pequena extensão na resposta de jsbueno:
#!/usr/bin/env python
import cairo
import rsvg
from xml.dom import minidom
def convert_svg_to_png(svg_file, output_file):
# Get the svg files content
with open(svg_file) as f:
svg_data = f.read()
# Get the width / height inside of the SVG
doc = minidom.parse(svg_file)
width = int([path.getAttribute('width') for path
in doc.getElementsByTagName('svg')][0])
height = int([path.getAttribute('height') for path
in doc.getElementsByTagName('svg')][0])
doc.unlink()
# create the png
img = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
ctx = cairo.Context(img)
handler = rsvg.Handle(None, str(svg_data))
handler.render_cairo(ctx)
img.write_to_png(output_file)
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("-f", "--file", dest="svg_file",
help="SVG input file", metavar="FILE")
parser.add_argument("-o", "--output", dest="output", default="svg.png",
help="PNG output file", metavar="FILE")
args = parser.parse_args()
convert_svg_to_png(args.svg_file, args.output)
Não achei nenhuma das respostas satisfatória. Todas as bibliotecas mencionadas têm algum problema ou outro, como Cairo abandonando o suporte para python 3.6 (elas abandonaram o suporte para Python 2 há cerca de 3 anos!). Além disso, instalar as bibliotecas mencionadas no Mac foi uma dor.
Finalmente, descobri que a melhor solução era svglib + reportlab . Ambos instalados sem problemas usando pip e a primeira chamada para converter de SVG para PNG funcionaram perfeitamente! Muito feliz com a solução.
Apenas 2 comandos fazem o truque:
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
drawing = svg2rlg("my.svg")
renderPM.drawToFile(drawing, "my.png", fmt="PNG")
Há alguma limitação dessas que eu deva estar ciente?
Usando pycairo e librsvg, consegui dimensionar SVG e renderizar para um bitmap. Supondo que seu SVG não seja exatamente 256x256 pixels, a saída desejada, você pode ler no SVG para um contexto Cairo usando rsvg e, em seguida, dimensioná-lo e gravar em PNG.
import cairo
import rsvg
width = 256
height = 256
svg = rsvg.Handle('cool.svg')
unscaled_width = svg.props.width
unscaled_height = svg.props.height
svg_surface = cairo.SVGSurface(None, width, height)
svg_context = cairo.Context(svg_surface)
svg_context.save()
svg_context.scale(width/unscaled_width, height/unscaled_height)
svg.render_cairo(svg_context)
svg_context.restore()
svg_surface.write_to_png('cool.png')
Do site Cario com algumas pequenas modificações. Também é um bom exemplo de como chamar uma biblioteca C do Python
from ctypes import CDLL, POINTER, Structure, byref, util
from ctypes import c_bool, c_byte, c_void_p, c_int, c_double, c_uint32, c_char_p
class _PycairoContext(Structure):
_fields_ = [("PyObject_HEAD", c_byte * object.__basicsize__),
("ctx", c_void_p),
("base", c_void_p)]
class _RsvgProps(Structure):
_fields_ = [("width", c_int), ("height", c_int),
("em", c_double), ("ex", c_double)]
class _GError(Structure):
_fields_ = [("domain", c_uint32), ("code", c_int), ("message", c_char_p)]
def _load_rsvg(rsvg_lib_path=None, gobject_lib_path=None):
if rsvg_lib_path is None:
rsvg_lib_path = util.find_library('rsvg-2')
if gobject_lib_path is None:
gobject_lib_path = util.find_library('gobject-2.0')
l = CDLL(rsvg_lib_path)
g = CDLL(gobject_lib_path)
g.g_type_init()
l.rsvg_handle_new_from_file.argtypes = [c_char_p, POINTER(POINTER(_GError))]
l.rsvg_handle_new_from_file.restype = c_void_p
l.rsvg_handle_render_cairo.argtypes = [c_void_p, c_void_p]
l.rsvg_handle_render_cairo.restype = c_bool
l.rsvg_handle_get_dimensions.argtypes = [c_void_p, POINTER(_RsvgProps)]
return l
_librsvg = _load_rsvg()
class Handle(object):
def __init__(self, path):
lib = _librsvg
err = POINTER(_GError)()
self.handle = lib.rsvg_handle_new_from_file(path.encode(), byref(err))
if self.handle is None:
gerr = err.contents
raise Exception(gerr.message)
self.props = _RsvgProps()
lib.rsvg_handle_get_dimensions(self.handle, byref(self.props))
def get_dimension_data(self):
svgDim = self.RsvgDimensionData()
_librsvg.rsvg_handle_get_dimensions(self.handle, byref(svgDim))
return (svgDim.width, svgDim.height)
def render_cairo(self, ctx):
"""Returns True is drawing succeeded."""
z = _PycairoContext.from_address(id(ctx))
return _librsvg.rsvg_handle_render_cairo(self.handle, z.ctx)
Handle.get_dimension_data
não funcione para mim. Tive que substituí-lo por uma simples busca de self.props.width
e self.props.height
. Tentei primeiro definir a RsvgDimensionData
Estrutura conforme descrito no site do cairo, mas sem sucesso.
Aqui está uma abordagem em que o Inkscape é chamado pelo Python.
Observe que ele suprime certas saídas crufty que o Inkscape grava no console (especificamente, stderr e stdout) durante a operação normal sem erros. A saída é capturada em duas variáveis de string out
e err
.
import subprocess # May want to use subprocess32 instead
cmd_list = [ '/full/path/to/inkscape', '-z',
'--export-png', '/path/to/output.png',
'--export-width', 100,
'--export-height', 100,
'/path/to/input.svg' ]
# Invoke the command. Divert output that normally goes to stdout or stderr.
p = subprocess.Popen( cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
# Below, < out > and < err > are strings or < None >, derived from stdout and stderr.
out, err = p.communicate() # Waits for process to terminate
# Maybe do something with stdout output that is in < out >
# Maybe do something with stderr output that is in < err >
if p.returncode:
raise Exception( 'Inkscape error: ' + (err or '?') )
Por exemplo, ao executar um determinado trabalho em meu sistema Mac OS, out
acabou sendo:
Background RRGGBBAA: ffffff00
Area 0:0:339:339 exported to 100 x 100 pixels (72.4584 dpi)
Bitmap saved as: /path/to/output.png
(O arquivo svg de entrada tinha um tamanho de 339 por 339 pixels.)
Na verdade, eu não queria depender de mais nada além de Python (Cairo, Ink .., etc.). Meus requisitos eram para ser o mais simples possível, no máximo, um simples pip install "savior"
seria suficiente, é por isso que nenhum dos acima t terno para mim.
Eu superei isso (indo além do Stackoverflow na pesquisa). https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/
Parece bom, até agora. Então, eu compartilho no caso de alguém na mesma situação.