Converter SVG em PNG em Python

99

Como faço para converter um svgpara png, em Python? Estou armazenando svgem uma instância de StringIO. Devo usar a biblioteca pyCairo? Como faço para escrever esse código?

ram1
fonte
3
Possivelmente uma duplicata de stackoverflow.com/questions/2932408/…
Optimal Cynic
2
Esse tópico deixou o problema sem solução. A resposta aceita veio do autor da pergunta que estava compartilhando sua tentativa de código falhada. A outra resposta sugeria ImageMagick, mas um comentarista disse que ImageMagick faz "um trabalho horrível de interpretação de SVG." Eu não quero que meus pngs pareçam horríveis, então estou fazendo a pergunta novamente.
ram1
1
Experimente cairographics.org/cookbook/librsvgpython
Optimal Cynic
Os exemplos nesse link são específicos para Win32. Estou executando o Linux.
ram1
Dê uma olhada nesta postagem do blog, parece que pode ser o que você precisa.
giodamelio

Respostas:

60

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á.

jsbueno
fonte
1
Ótimo, funciona bem. Mas existe uma maneira de cairodeterminar a ALTURA e a LARGURA da imagem por conta própria? Verifiquei o *.svgarquivo 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.
quapka
1
Se a "largura" e a "altura" dos seus arquivos estão definidas para 100%, não há mágica que Cairo ou rsvg possam fazer para adivinhar o tamanho: esses arquivos SVG foram deixados em tamanho independente pelo software criador (/ pessoa). O código HTML circundante para importar o arquivo SVG forneceria o tamanho físico. No entanto, o objeto "Handle" do rsvg tem um .get_dimension_data()método que funcionou para o meu arquivo de exemplo (um SVG bem comportado) - experimente.
jsbueno
1
a partir de 2014, para ubuntu, você pode usar: apt-get install python-rsvg
t1m0
1
Existe um comando rápido para adicionar um fundo branco à imagem se o fundo atual for transparente?
fiatjaf
@jsbueno Eu uso o Windows 8.1 e python 2.7.11 Como posso instalar o Cairo e o rsvg e fazer com que funcionem. Eu estava lutando para fazer isso funcionar. BTW +1 para sua explicação detalhada.
Marlon Abeykoon,
88

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

nemesisfixx
fonte
5
Oi. Você sabe como posso fazer o mesmo, mas sem gravar em um arquivo? Preciso enviar o conteúdo do png para o navegador a partir de um servidor da web, para que o usuário possa baixar a imagem. Salvar o arquivo png não é uma opção válida em nosso projeto, por isso preciso disso. Obrigado
estemendoza
4
Eu tenho feito isso sozinho. Basicamente depende de quais ferramentas ou estruturas você tem em mãos ao lidar com suas solicitações da web, mas não importa o que seja, a ideia básica é que svg2pngleva um streamobjeto no write_toparâ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-Dispositioncabeçalho. veja aqui: stackoverflow.com/questions/1012437/…
nemesisfixx
4
Para quem tem problemas com esse código, como eu: 1). bytestringaceita 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')
Serj Zaharchenko
3
cairosvg oferece suporte apenas a Python 3.4+. Eles abandonaram o suporte ao Python 2
Marlon Abeykoon,
1
Não havia um svg2pngpara mim, eu tinha que usar cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png').
tobltobs
39

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 -icom 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}
blj
fonte
2
+1 porque isso também é extremamente útil para scripts de shell. Veja inkscape.org/doc/inkscape-man.html para documentos completos sobre a linha de comando do Inkscape.
Dia
Obrigado, esta foi a maneira mais fácil que encontrei de fazer isso. No Windows, para que você não precise digitar o caminho completo para o Inkscape todas as vezes, você pode adicioná-lo ao seu Caminho nas Variáveis ​​Ambientais .
Alex S
3
Esta não é uma resposta. É uma solução alternativa. OP pediu uma solução Python.
lamino
31

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)
streetlogics
fonte
2
Wand funcionou muito melhor do que Cairo para meus PNGs.
qris
3
Recebo o erro image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
adrienlucca.wordpress.com
2
svg_fileé considerado um objeto "arquivo" neste exemplo, a configuração svg_fileseria algo como:svg_file = File.open(file_name, "r")
streetlogics
2
Obrigado, o método cairoe rsvg'aceito' não funcionou para o meu PDF. pip install wande seu snippet funcionou;)
nmz787
5
Se você tem um SVG 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.
asherbar
12

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
user732592
fonte
Infelizmente, há dois problemas com isso. Primeiro, ele não lida com o <clipPath><rect ... /></clipPath>. Em segundo lugar, não é necessária a opção -d (DPI).
Raio de
2
@Ray, envie relatórios de erros / solicitações de recursos no rastreador CairoSVG !
Simon Sapin
@Simon, você pode fazer isso por favor? Estou muito ocupado e estarei nos próximos 1-2 meses.
Ray
2
@Ray, na verdade a opção -d / --dpi já existe há um tempo, e fui informado que o suporte para <clippath> foi adicionado algumas semanas atrás na versão git.
Simon Sapin
@Simon, legal! Obrigado! :)
Ray
6

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.

Adam Kerz
fonte
5

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)
Martin Thoma
fonte
Eu usei a extração de largura e altura SVG. Não tenho certeza sobre o padrão svg, mas em alguns dos meus arquivos svg a largura ou altura eram seguidas por uma string não numérica, como 'mm' ou 'px' (ex: '250mm'). O int ('250mm') lança uma exceção e eu tive que fazer alguns ajustes adicionais.
WigglyWorld
3

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?

Sarang
fonte
Sim, o marcador final não é compatível, e não será, github.com/deeplook/svglib/issues/177
Rainald62
2

Escala SVG e renderização PNG

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.

main.py

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')

Ligação RSVG C

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)
Cameron Lowell Palmer
fonte
Obrigado por isso, foi muito útil em um projeto meu. Embora Handle.get_dimension_datanão funcione para mim. Tive que substituí-lo por uma simples busca de self.props.widthe self.props.height. Tentei primeiro definir a RsvgDimensionDataEstrutura conforme descrito no site do cairo, mas sem sucesso.
JeanOlivier
Estou tentando usar isso em um projeto meu. Como obtenho os arquivos DLL necessários?
Andoo de
0

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 oute 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, outacabou 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.)

Travesseiro de ferro
fonte
Não é Python ponta a ponta se você depende do Inkscape.
Joel
1
@Joel: A primeira linha foi modificada para superar sua objeção. Mas é claro, mesmo uma solução Python "pura" depende de elementos fora da linguagem central e, em última análise, é executada com linguagem de máquina, então talvez não exista algo de ponta a ponta!
Travesseiro de ferro de
0

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.

Ualter Jr.
fonte