Convertir SVG a PNG en Python

99

¿Cómo convierto un svga pngen Python? Estoy almacenando el svgen una instancia de StringIO. ¿Debo usar la biblioteca de PyCairo? ¿Cómo escribo ese código?

ram1
fuente
3
Posiblemente un duplicado de stackoverflow.com/questions/2932408/…
Optimal Cynic
2
Ese hilo dejó el problema sin resolver. La respuesta aceptada provino del autor de la pregunta que estaba compartiendo su intento fallido de código. La otra respuesta sugirió ImageMagick, pero un comentarista dijo que ImageMagick hace "un trabajo horrible al interpretar SVG". No quiero que mis png se vean horribles, así que vuelvo a hacer la pregunta.
ram1
Los ejemplos de ese enlace son específicos de Win32. Estoy ejecutando linux.
ram1
Eche un vistazo a esta publicación de blog, parece que podría ser lo que necesita.
giodamelio

Respuestas:

60

La respuesta es " pyrsvg ", un enlace de Python para librsvg .

Hay un paquete de Ubuntu python-rsvg que lo proporciona. Buscar en Google su nombre es pobre porque su código fuente parece estar contenido dentro del repositorio GIT del proyecto Gnome "gnome-python-desktop".

Hice un "hola mundo" minimalista que renderiza SVG en una superficie de El Cairo y lo escribe en el 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")

Actualización : a partir de 2014 el paquete necesario para la distribución de Fedora Linux es: gnome-python2-rsvg. La lista de fragmentos anterior todavía funciona como está.

jsbueno
fuente
1
Genial, funciona muy bien. Pero, ¿hay alguna manera de permitir cairodeterminar la ALTURA y ANCHO de la imagen por sí sola? He examinado el *.svgarchivo para extraer el ALTO y el ANCHO de allí, pero ambos están configurados en 100%. Por supuesto, puedo analizar las propiedades de la imagen, pero como este es solo un paso en el procesamiento de la imagen, no es lo que quiero.
quapka
1
Si el "ancho" y la "altura" de sus archivos están configurados al 100%, no hay magia que Cairo o rsvg puedan hacer para adivinar el tamaño: el software creador (/ persona) dejó el tamaño de estos archivos SVG independientemente del tamaño. El código HTML circundante para importar el archivo SVG proporcionaría el tamaño físico. Sin embargo, el objeto "Handle" de rsvg tiene un .get_dimension_data()método que funcionó para mi archivo de ejemplo (un SVG con buen comportamiento) - pruébalo.
jsbueno
1
a partir de 2014, para ubuntu, puede usar: apt-get install python-rsvg
t1m0
1
¿Existe un comando rápido para agregar un fondo blanco a la imagen si su fondo actual es transparente?
fiatjaf
@jsbueno Yo uso Windows 8.1 y python 2.7.11 ¿Cómo puedo instalar cairo y rsvg y hacer que funcione? Estaba luchando para que esto funcionara. Por cierto +1 para su explicación detallada.
Marlon Abeykoon
88

Esto es lo que hice 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')

¡Y funciona como un encanto!

Ver más: documento cairosvg

némesisfixx
fuente
5
Hola. ¿Sabes cómo puedo hacer lo mismo pero sin escribir en un archivo? Necesito enviar contenido png al navegador desde un servidor web, para que el usuario pueda descargar la imagen. Guardar el archivo png no es una opción válida en nuestro proyecto, por eso lo necesito de esa manera. Gracias
estemendoza
4
Yo mismo he estado haciendo esto. Básicamente depende de las herramientas o marcos que tenga a mano al manejar sus solicitudes web, pero no importa cuál sea, la idea básica es que svg2pngtome un streamobjeto en el write_toparámetro, y este puede ser su objeto de respuesta HTTP (que en la mayoría de los frameworks es un objeto similar a un archivo) o alguna otra secuencia, que luego sirve al navegador usando el Content-Dispositionencabezado. ver aquí: stackoverflow.com/questions/1012437/…
nemesisfixx
4
Para aquellos que experimentan los problemas con ese código, como yo: 1). bytestringacepta bytes, así que convierta la cadena primero con bytestring=bytes(svg,'UTF-8') 2). el modo de archivo debe ser binario, entoncesopen('output.png','wb')
Serj Zaharchenko
3
cairosvg solo admite Python 3.4+. Han eliminado el soporte de Python 2
Marlon Abeykoon
1
No había un svg2pngpara mí, tenía que usarlo cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png').
tobltobs
39

Instale Inkscape y llámelo como línea de comando:

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -j -e ${dest_png}

También puede ajustar un área rectangular específica solo mediante el parámetro -j, por ejemplo, coordinar "0: 125: 451: 217"

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -j -a ${coordinates} -e ${dest_png}

Si desea mostrar solo un objeto en el archivo SVG, puede especificar el parámetro -icon la identificación del objeto que ha configurado en el SVG. Oculta todo lo demás.

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -i ${object} -j -a ${coordinates} -e ${dest_png}
blj
fuente
2
+1 porque esto también es extremadamente útil para scripts de shell. Consulte inkscape.org/doc/inkscape-man.html para obtener documentos completos sobre la línea de comandos de Inkscape.
Prime
Gracias, esta fue la forma más fácil que encontré para hacer esto. En Windows, para que no tenga que escribir la ruta completa a Inkscape cada vez, puede agregarla a su Ruta en Variables ambientales .
Alex S
3
Ésta no es una respuesta. Es una solución. OP pidió una solución de Python.
lamino
31

Estoy usando Wand-py (una implementación de la envoltura de Wand alrededor de ImageMagick) para importar algunos SVG bastante avanzados y hasta ahora he visto excelentes resultados. Este es todo el código que se necesita:

    with wand.image.Image( blob=svg_file.read(), format="svg" ) as image:
        png_image = image.make_blob("png")

Acabo de descubrir esto hoy, y sentí que valía la pena compartirlo con cualquier otra persona que pudiera encontrar esta respuesta, ya que ha pasado un tiempo desde que se respondieron la mayoría de estas preguntas.

NOTA: Técnicamente, en las pruebas, descubrí que ni siquiera tiene que pasar el parámetro de formato para ImageMagick, por lo que with wand.image.Image( blob=svg_file.read() ) as image: era todo lo que realmente se necesitaba.

EDITAR: A partir de un intento de edición por qris, aquí hay un código útil que le permite usar ImageMagick con un SVG que tiene un fondo 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
fuente
2
Wand funcionó mucho mejor que Cairo para mis PNG.
qris
3
Recibo el error image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
adrienlucca.wordpress.com
2
svg_filese asume que es un objeto "archivo" en este ejemplo, la configuración svg_filese vería así:svg_file = File.open(file_name, "r")
streetlogics
2
Gracias, el método cairoy rsvg'aceptado' no funcionó para mi PDF. pip install wandy tu fragmento funcionó;)
nmz787
5
Si usted tiene una SVG strentonces primero tiene que codifican en binario de la siguiente manera: svg_blob = svg_str.encode('utf-8'). Ahora puede usar el método anterior reemplazando blob=svg_file.read()con blob=svg_blob.
asherbar
12

Prueba esto: http://cairosvg.org/

El sitio dice:

CairoSVG está escrito en Python puro y solo depende de Pycairo. Se sabe que funciona en Python 2.6 y 2.7.

Actualización 25 de noviembre de 2016 :

2.0.0 es una nueva versión principal, su registro de cambios incluye:

  • Soporte de Drop Python 2
usuario732592
fuente
Hay dos problemas con esto, desafortunadamente. Primero, no maneja el <clipPath><rect ... /></clipPath>. En segundo lugar, no acepta la opción -d (DPI).
Ray
2
@Ray, envíe informes de errores / solicitudes de funciones en el rastreador CairoSVG .
Simon Sapin
@Simon, ¿puedes hacerlo por favor? Estoy demasiado ocupado y lo estaré en los próximos 1-2 meses.
Ray
2
@Ray, en realidad, la opción -d / --dpi ha estado ahí por un tiempo, y me dijeron que el soporte para <clippath> se agregó hace unas semanas en la versión de git.
Simon Sapin
@Simon, ¡bien! ¡Gracias! :)
Ray
6

Otra solución que acabo de encontrar aquí ¿Cómo renderizar un SVG escalado a una 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 se instala fácilmente desde un paquete binario en Windows (y lo uso para otras cosas, por lo que es fácil para mí).

Sin embargo, noté algunos problemas al convertir las banderas de países de Wikimedia, por lo que quizás no sea el analizador / renderizador svg más robusto.

Adam Kerz
fuente
5

Una pequeña extensión sobre la respuesta 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)
Martín Thoma
fuente
Usé la extracción de ancho y alto de svg. No estoy seguro sobre el estándar svg, pero en algunos de mis archivos svg, el ancho o la altura fueron seguidos por una cadena no numérica como 'mm' o 'px' (por ejemplo: '250 mm'). El int ('250 mm') arroja una excepción y tuve que hacer algunos ajustes adicionales.
WigglyWorld
3

No encontré ninguna de las respuestas satisfactoria. Todas las bibliotecas mencionadas tienen algún problema o el otro, como Cairo, que deja de admitir python 3.6 (¡dejaron de admitir Python 2 hace unos 3 años!). Además, instalar las bibliotecas mencionadas en Mac fue una molestia.

Finalmente, encontré que la mejor solución fue svglib + reportlab . ¡Ambos instalados sin problemas usando pip y la primera llamada para convertir de svg a png funcionaron maravillosamente! Muy contento con la solución.

Solo 2 comandos hacen el truco:

from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
drawing = svg2rlg("my.svg")
renderPM.drawToFile(drawing, "my.png", fmt="PNG")

¿Hay alguna limitación que deba tener en cuenta?

Sarang
fuente
Sí, el fin de marcador no es compatible y no lo será, github.com/deeplook/svglib/issues/177
Rainald62
2

Escalado de SVG y renderizado PNG

Usando pycairo y librsvg pude lograr el escalado SVG y el renderizado en un mapa de bits. Suponiendo que su SVG no es exactamente de 256x256 píxeles, la salida deseada, puede leer en el SVG en un contexto de El Cairo usando rsvg y luego escalarlo y escribir en un 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')

Enlace RSVG C

Desde el sitio web de Cario con algunas modificaciones menores. También es un buen ejemplo de cómo llamar a una biblioteca C desde 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
fuente
Gracias por esto, resultó muy útil en un proyecto mío. Aunque Handle.get_dimension_datano funcionó para mí. Tuve que reemplazarlo con una simple búsqueda de self.props.widthy self.props.height. Primero intenté definir la RsvgDimensionDataEstructura como se describe en el sitio web de El Cairo, pero sin éxito.
JeanOlivier
Estoy tratando de usar esto en un proyecto mío. ¿Cómo obtengo los archivos dll necesarios?
Andoo
0

Aquí hay un enfoque en el que Python llama a Inkscape.

Tenga en cuenta que suprime cierta salida cruda que Inkscape escribe en la consola (específicamente, stderr y stdout) durante el funcionamiento normal sin errores. La salida se captura en dos variables de cadena outy 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 ejemplo, al ejecutar un trabajo en particular en mi sistema Mac OS, outterminó siendo:

Background RRGGBBAA: ffffff00
Area 0:0:339:339 exported to 100 x 100 pixels (72.4584 dpi)
Bitmap saved as: /path/to/output.png

(El archivo svg de entrada tenía un tamaño de 339 por 339 píxeles).

Almohada de hierro
fuente
No es Python de un extremo a otro si confías en Inkscape.
Joel
1
@Joel: La primera línea ha sido modificada para superar su objeción. Pero, por supuesto, incluso una solución de Python "pura" se basa en elementos fuera del lenguaje central y, en última instancia, se ejecuta con lenguaje de máquina, por lo que tal vez no exista nada de extremo a extremo.
Iron Pillow
0

En realidad, no quería depender de nada más que de Python (Cairo, Ink ..., etc.). Mis requisitos debían ser lo más simples posible, a lo sumo, un simple pip install "savior"sería suficiente, por eso ninguno de los anteriores lo hizo ' me queda bien.

Pasé por esto (yendo más allá de Stackoverflow en la investigación). https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/

Se ve bien hasta ahora. Así que lo comparto por si alguien se encuentra en la misma situación.

Ualter Jr.
fuente