Converti SVG in PNG in Python


99

Come posso convertire un svgin png, in Python? Sto memorizzando svgin un'istanza di StringIO. Devo usare la libreria pyCairo? Come scrivo quel codice?



2
Quel thread ha lasciato il problema irrisolto. La risposta accettata è arrivata dal richiedente che condivideva il suo tentativo di codice fallito. L'altra risposta ha suggerito ImageMagick, ma un commentatore ha detto che ImageMagick fa "un lavoro orribile nell'interpretare SVG". Non voglio che i miei png abbiano un aspetto orribile, quindi sto nuovamente ponendo la domanda.
ram1


Gli esempi in quel collegamento sono specifici per Win32. Sto eseguendo Linux.
ram1

Dai un'occhiata a questo post del blog, sembra che potrebbe essere quello di cui hai bisogno.
giodamelio

Risposte:


60

La risposta è " pyrsvg " - un'associazione Python per librsvg .

C'è un pacchetto Ubuntu python-rsvg che lo fornisce. La ricerca su Google per il suo nome è scarsa perché il suo codice sorgente sembra essere contenuto all'interno del repository Gnome del progetto Gnome "gnome-python-desktop".

Ho creato un "ciao mondo" minimalista che rende SVG una superficie del cairo e lo scrive su 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")

Aggiornamento : a partire dal 2014 il pacchetto necessario per la distribuzione Fedora Linux è: gnome-python2-rsvg. L'elenco di frammenti di cui sopra funziona ancora così com'è.


1
Ottimo, funziona bene. Ma esiste un modo per cairodeterminare da solo l'ALTEZZA e la LARGHEZZA dell'immagine? Ho esaminato il *.svgfile per estrarre l'ALTEZZA e la LARGHEZZA da lì, ma sono entrambe impostate su 100%. Certo, posso esaminare le proprietà dell'immagine, ma poiché questo è solo un passaggio nell'elaborazione dell'immagine, non è quello che voglio.
quapka

1
Se la "larghezza" e "l'altezza" dei tuoi file sono impostate al 100%, non c'è nessuna magia che Cairo o rsvg possono fare per indovinare la dimensione: tali file SVG sono stati lasciati indipendenti dalle dimensioni dal software creatore (/ person). Il codice HTML circostante per importare il file SVG fornirebbe la dimensione fisica. Tuttavia, l'oggetto "Handle" di rsvg ha un .get_dimension_data()metodo che ha funzionato per il mio file di esempio (un SVG ben comportato) - provalo.
jsbueno

1
a partire dal 2014, per Ubuntu, è possibile utilizzare: apt-get install python-rsvg
t1m0

1
Esiste un comando rapido per aggiungere uno sfondo bianco all'immagine se lo sfondo corrente è trasparente?
fiatjaf

@jsbueno Uso Windows 8.1 e python 2.7.11 Come posso installare cairo e rsvg e farlo funzionare. Stavo lottando per far funzionare questo. BTW +1 per la tua spiegazione dettagliata.
Marlon Abeykoon

88

Ecco cosa ho fatto 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 funziona come un incantesimo!

Vedi di più: documento cairosvg


5
Ciao. Sai come posso fare lo stesso ma senza scrivere su un file? Devo inviare il contenuto png al browser da un server web, in modo che l'utente possa scaricare l'immagine. Il salvataggio del file png non è un'opzione valida nel nostro progetto, ecco perché ne ho bisogno in questo modo. Grazie
estemendoza

4
L'ho fatto io stesso. Fondamentalmente dipende da quali strumenti o framework hai a portata di mano quando gestisci le tue richieste web, ma non importa quale sia, l'idea di base è che svg2pngaccetta un streamoggetto nel write_toparametro, e questo può essere il tuo oggetto Risposta HTTP (che in la maggior parte dei framework è un oggetto simile a un file) o qualche altro flusso, che poi servi al browser usando l' Content-Dispositionintestazione. vedere qui: stackoverflow.com/questions/1012437/...
nemesisfixx

4
Per coloro che sperimentano i problemi con quel codice, come lo ero io: 1). bytestringaccetta byte, quindi converti prima la stringa con bytestring=bytes(svg,'UTF-8') 2). la modalità file dovrebbe essere binaria, quindiopen('output.png','wb')
Serj Zaharchenko

3
cairosvg supporta solo Python 3.4+. Hanno abbandonato il supporto per Python 2
Marlon Abeykoon

1
Non c'era un svg2pngper me, dovevo usare cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png').
tobltobs

39

Installa Inkscape e chiamalo come riga di comando:

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

Puoi anche catturare un'area rettangolare specifica utilizzando solo il parametro -j, ad es. Coordinate "0: 125: 451: 217"

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

Se vuoi mostrare un solo oggetto nel file SVG, puoi specificare il parametro -icon l'id oggetto che hai impostato nell'SVG. Nasconde tutto il resto.

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

2
+1 perché è estremamente utile anche per gli script di shell. Vedi inkscape.org/doc/inkscape-man.html per la documentazione completa sulla riga di comando di Inkscape.
Prime

Grazie, questo è stato il modo più semplice che ho trovato per farlo. Su Windows, per fare in modo che non sia necessario digitare ogni volta il percorso completo di Inkscape, è possibile aggiungerlo al percorso in Variabili ambientali .
Alex S

3
Questa non è una risposta. È una soluzione alternativa. OP ha chiesto una soluzione Python.
lamino

31

Sto usando Wand-py (un'implementazione del wrapper Wand attorno a ImageMagick) per importare alcuni SVG piuttosto avanzati e finora ho visto ottimi risultati! Questo è tutto il codice che serve:

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

L'ho scoperto solo oggi e ho sentito come se valesse la pena condividerlo con chiunque altro potesse trovare questa risposta perché è passato un po 'di tempo da quando la maggior parte di queste domande ha avuto risposta.

NOTA: tecnicamente durante i test ho scoperto che non devi nemmeno passare il parametro di formato per ImageMagick, quindi with wand.image.Image( blob=svg_file.read() ) as image: era tutto ciò che era veramente necessario.

EDIT: da un tentativo di modifica da parte di qris, ecco un codice utile che ti consente di utilizzare ImageMagick con un SVG che ha uno sfondo trasparente:

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)

2
Wand ha funzionato molto meglio di Cairo per i miei PNG.
qris

3
Ricevo l'errore image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
adrienlucca.wordpress.com

2
svg_filesi presume che sia un oggetto "file" in questo esempio, l'impostazione svg_filesarebbe simile a:svg_file = File.open(file_name, "r")
streetlogics

2
Grazie, il metodo cairoe rsvg"accettato" non ha funzionato per il mio PDF. pip install wande il tuo snippet ha funzionato;)
nmz787

5
Se avete uno svg strallora necessario prima di codificare in binario in questo modo: svg_blob = svg_str.encode('utf-8'). Ora puoi usare il metodo sopra sostituendo blob=svg_file.read()con blob=svg_blob.
Asherbar il

12

Prova questo: http://cairosvg.org/

Il sito dice:

CairoSVG è scritto in puro python e dipende solo da Pycairo. È noto per funzionare su Python 2.6 e 2.7.

Aggiornamento del 25 novembre 2016 :

2.0.0 è una nuova versione principale, il suo log delle modifiche include:

  • Elimina il supporto per Python 2

Ci sono due problemi con questo, sfortunatamente. Innanzitutto, non gestisce il file <clipPath><rect ... /></clipPath>. Secondo, non accetta l'opzione -d (DPI).
Ray

2
@Ray, invia segnalazioni di bug / richieste di funzionalità sul tracker CairoSVG !
Simon Sapin

@ Simon, puoi farlo per favore? Sono troppo impegnato e lo sarò nei prossimi 1-2 mesi.
Ray

2
@ Ray, in realtà l'opzione -d / --dpi è disponibile da un po 'di tempo e mi è stato detto che il supporto per <clippath> è stato aggiunto alcune settimane fa nella versione git.
Simon Sapin

@ Simon, gentile! Grazie! :)
Ray

6

Un'altra soluzione che ho appena trovato qui Come rendere un SVG ridimensionato in 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 si installa facilmente da un pacchetto binario in Windows (e lo uso per altre cose, quindi è facile per me).

Tuttavia, ho notato alcuni problemi durante la conversione delle bandiere dei paesi da Wikimedia, quindi forse non è il parser / renderer svg più robusto.


5

Una piccola estensione sulla risposta di 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)

Ho usato l'estrazione di larghezza e altezza in formato svg. Non sono sicuro dello standard svg ma in alcuni dei miei file svg la larghezza o l'altezza erano seguite da una stringa non numerica come "mm" o "px" (es: "250 mm"). L'int ('250mm') genera un'eccezione e ho dovuto apportare alcune modifiche aggiuntive.
WigglyWorld

3

Non ho trovato soddisfacente nessuna delle risposte. Tutte le librerie menzionate hanno qualche problema o l'altro come Cairo ha abbandonato il supporto per python 3.6 (hanno abbandonato il supporto per Python 2 circa 3 anni fa!). Inoltre, l'installazione delle librerie menzionate sul Mac è stata una seccatura.

Infine, ho trovato che la soluzione migliore era svglib + reportlab . Entrambi installati senza intoppi utilizzando pip e la prima chiamata per convertire da svg a png hanno funzionato perfettamente! Molto soddisfatto della soluzione.

Solo 2 comandi fanno il trucco:

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

Ci sono limitazioni con questi di cui dovrei essere a conoscenza?


Sì, il marker-end non è supportato e non lo sarà, github.com/deeplook/svglib/issues/177
Rainald62

2

Ridimensionamento SVG e rendering PNG

Utilizzando pycairo e librsvg sono stato in grado di ottenere il ridimensionamento SVG e il rendering in una bitmap. Supponendo che il tuo SVG non sia esattamente 256x256 pixel, l'output desiderato, puoi leggere nell'SVG in un contesto Cairo usando rsvg, quindi ridimensionarlo e scrivere in 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')

Attacco RSVG C.

Dal sito Cario con qualche piccola modifica. Anche un buon esempio di come chiamare una libreria C da 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)

Grazie per questo, si è rivelato molto utile in un mio progetto. Anche se Handle.get_dimension_datanon ha funzionato per me. Ho dovuto sostituirlo con un semplice recupero di self.props.widthe self.props.height. Ho provato per la prima volta a definire la RsvgDimensionDatastruttura come descritto sul sito web del cairo, ma senza successo.
JeanOlivier

Sto cercando di usarlo in un mio progetto. Come ottengo i file dll richiesti?
Andoo,

0

Ecco un approccio in cui Inkscape viene chiamato da Python.

Si noti che sopprime alcuni output crufty che Inkscape scrive sulla console (in particolare, stderr e stdout) durante il normale funzionamento senza errori. L'output viene catturato in due variabili stringa 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 '?')  )

Ad esempio, durante l'esecuzione di un particolare lavoro sul mio sistema Mac OS, è outfinito per essere:

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

(Il file svg di input aveva una dimensione di 339 x 339 pixel.)


Non è Python end-to-end se ti affidi a Inkscape.
Joel

1
@ Joel: la prima riga è stata modificata per superare la tua obiezione. Ma ovviamente, anche una soluzione Python "pura" si basa su elementi al di fuori del linguaggio di base e alla fine viene eseguita con il linguaggio macchina, quindi forse non esiste nulla di end-to-end!
Cuscino di ferro il

0

In realtà, non volevo dipendere da nient'altro che Python (Cairo, Ink .., ecc.) I miei requisiti dovevano essere il più semplice possibile, al massimo, pip install "savior"sarebbe bastato un semplice , ecco perché nessuno di quelli sopra lo ha fatto ' è adatto a me.

Ci sono arrivato (andando oltre Stackoverflow nella ricerca). https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/

Sembra buono, finora. Quindi lo condivido nel caso qualcuno si trovi nella stessa situazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.