Convertir SVG en PNG en Python

99

Comment convertir un svgen png, en Python? Je stocke le svgdans une instance de StringIO. Dois-je utiliser la bibliothèque pyCairo? Comment écrire ce code?

ram1
la source
3
Peut-être un double de stackoverflow.com/questions/2932408/…
Optimal Cynic
2
Ce fil n'a pas résolu le problème. La réponse acceptée est venue du demandeur qui partageait sa tentative de code échouée. L'autre réponse suggérait ImageMagick mais un commentateur a déclaré qu'ImageMagick fait "un travail horrible d'interprétation de SVG". Je ne veux pas que mes png soient horribles, alors je pose à nouveau la question.
ram1
Les exemples de ce lien sont spécifiques à Win32. J'utilise Linux.
ram1
Jetez un œil à cet article de blog, il semble que ce soit ce dont vous avez besoin.
giodamelio

Réponses:

60

La réponse est " pyrsvg " - une liaison Python pour librsvg .

Il existe un package Ubuntu python-rsvg qui le fournit. La recherche de son nom sur Google est médiocre car son code source semble être contenu dans le référentiel GIT du projet Gnome "gnome-python-desktop".

J'ai créé un "hello world" minimaliste qui rend SVG sur une surface du Caire et l'écrit sur le disque:

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

Mise à jour : à partir de 2014 le paquet nécessaire pour la distribution Fedora Linux est: gnome-python2-rsvg. La liste d'extraits ci-dessus fonctionne toujours telle quelle.

jsbueno
la source
1
Super, fonctionne bien. Mais y a-t-il un moyen de laisser cairodéterminer la HAUTEUR et la LARGEUR de l'image seule? J'ai regardé dans le *.svgfichier pour en extraire la HAUTEUR et la LARGEUR, mais il est à la fois défini sur 100%. Bien sûr, je peux examiner les propriétés de l'image, mais comme ce n'est qu'une étape dans le traitement de l'image, ce n'est pas ce que je veux.
quapka
1
Si la "largeur" ​​et la "hauteur" de vos fichiers sont réglées à 100%, il n'y a pas de magie que Cairo ou rsvg peut faire pour deviner la taille: de tels fichiers SVG ont été laissés indépendants de la taille par le logiciel créateur (/ person). Le code HTML environnant pour importer le fichier SVG fournirait la taille physique. Cependant, l'objet "Handle" de rsvg a une .get_dimension_data()méthode qui a fonctionné pour mon fichier d'exemple (un SVG bien comporté) - essayez-le.
jsbueno
1
à partir de 2014, pour ubuntu, vous pouvez utiliser: apt-get install python-rsvg
t1m0
1
Existe-t-il une commande rapide pour ajouter un fond blanc à l'image si son arrière-plan actuel est transparent?
fiatjaf
@jsbueno J'utilise Windows 8.1 et python 2.7.11 Comment puis-je installer cairo et rsvg et le faire fonctionner. J'avais du mal à faire ce travail. BTW +1 pour votre explication détaillée.
Marlon Abeykoon
88

Voici ce que j'ai fait en utilisant 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')

Et cela fonctionne comme un charme!

Voir plus: document cairosvg

nemesisfixx
la source
5
Salut. Savez-vous comment puis-je faire la même chose sans écrire dans un fichier? J'ai besoin de pousser le contenu png vers le navigateur à partir d'un serveur Web, afin que l'utilisateur puisse télécharger l'image. L'enregistrement du fichier png n'est pas une option valide dans notre projet, c'est pourquoi j'en ai besoin de cette façon. Merci
estemendoza
4
J'ai fait ça moi-même. Cela dépend essentiellement des outils ou des frameworks que vous avez à portée de main lors du traitement de vos requêtes Web, mais peu importe ce que c'est, l'idée de base est de svg2pngprendre un streamobjet dans le write_toparamètre, et cela peut être votre objet de réponse HTTP (qui dans la plupart des frameworks est un objet de type fichier) ou un autre flux, que vous diffusez ensuite au navigateur à l'aide de l'en- Content-Dispositiontête. voir ici: stackoverflow.com/questions/1012437/…
nemesisfixx
4
Pour ceux qui rencontrent les problèmes avec ce code, comme je l'étais: 1). bytestringaccepte les octets, donc convertissez d'abord la chaîne avec bytestring=bytes(svg,'UTF-8') 2). le mode de fichier doit être binaire, doncopen('output.png','wb')
Serj Zaharchenko
3
cairosvg ne prend en charge que Python 3.4+. Ils ont abandonné le support de Python 2
Marlon Abeykoon
1
Il n'y en avait pas svg2pngpour moi, je devais utiliser cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png').
tobltobs
39

Installez Inkscape et appelez-le en ligne de commande:

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

Vous pouvez également capturer une zone rectangulaire spécifique uniquement à l'aide du paramètre -j, par exemple la coordonnée "0: 125: 451: 217"

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

Si vous ne souhaitez afficher qu'un seul objet dans le fichier SVG, vous pouvez spécifier le paramètre -iavec l'ID d'objet que vous avez configuré dans le SVG. Il cache tout le reste.

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -i ${object} -j -a ${coordinates} -e ${dest_png}
blj
la source
2
+1 car c'est également extrêmement pratique pour les scripts shell. Voir inkscape.org/doc/inkscape-man.html pour la documentation complète sur la ligne de commande d'Inkscape.
Prime le
Merci, c'est le moyen le plus simple que j'ai trouvé pour le faire. Sous Windows, pour que vous n'ayez pas à saisir le chemin complet d'Inkscape à chaque fois, vous pouvez l' ajouter à votre chemin dans les variables d'environnement .
Alex S
3
Ceci n'est pas une réponse. C'est une solution de contournement. OP a demandé une solution Python.
lamino
31

J'utilise Wand-py (une implémentation du wrapper Wand autour d'ImageMagick) pour importer des SVG assez avancés et j'ai jusqu'à présent vu d'excellents résultats! C'est tout le code qu'il faut:

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

Je viens de découvrir cela aujourd'hui, et j'ai eu l'impression que cela valait la peine d'être partagé pour quiconque pourrait se débattre avec cette réponse car cela faisait un moment que la plupart de ces questions n'avaient pas été répondues.

REMARQUE: techniquement, lors des tests, j'ai découvert que vous n'avez même pas besoin de passer le paramètre de format pour ImageMagick, donc with wand.image.Image( blob=svg_file.read() ) as image:c'était tout ce qui était vraiment nécessaire.

EDIT: À partir d'une tentative d'édition par qris, voici un code utile qui vous permet d'utiliser ImageMagick avec un SVG qui a un fond transparent:

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
la source
2
Wand fonctionnait beaucoup mieux que Cairo pour mes PNG.
qris
3
image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
J'obtiens
2
svg_fileest supposé être un objet "fichier" dans cet exemple, le paramètre svg_fileressemblerait à svg_file = File.open(file_name, "r")
ceci
2
Merci, la cairoet rsvgméthode « accepté » n'a pas fonctionné pour mon PDF. pip install wandet votre extrait a fait l'affaire;)
nmz787
5
Si vous avez un svg str, vous devez d'abord coder en binaire comme ceci: svg_blob = svg_str.encode('utf-8'). Vous pouvez maintenant utiliser la méthode ci-dessus en remplaçant blob=svg_file.read()par blob=svg_blob.
asherbar
12

Essayez ceci: http://cairosvg.org/

Le site dit:

CairoSVG est écrit en python pur et ne dépend que de Pycairo. Il est connu pour fonctionner sur Python 2.6 et 2.7.

Mise à jour du 25 novembre 2016 :

2.0.0 est une nouvelle version majeure, son changelog comprend:

  • Supprimer le support de Python 2
user732592
la source
Il y a deux problèmes avec cela, malheureusement. Premièrement, il ne gère pas les fichiers <clipPath><rect ... /></clipPath>. Deuxièmement, il ne prend pas l'option -d (DPI).
Ray
2
@Ray, veuillez envoyer des rapports de bogues / demandes de fonctionnalités sur le tracker CairoSVG !
Simon Sapin
@Simon, pouvez-vous le faire s'il vous plaît? Je suis trop occupé et je le serai dans les 1-2 prochains mois.
Ray
2
@Ray, en fait l'option -d / --dpi existe depuis un certain temps maintenant, et on me dit que le support de <clippath> a été ajouté il y a quelques semaines dans la version git.
Simon Sapin
@Simon, sympa! Je vous remercie! :)
Ray
6

Une autre solution que je viens de trouver ici Comment rendre un SVG mis à l'échelle en 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 s'installe facilement à partir d'un package binaire dans Windows (et je l'utilise pour d'autres choses, c'est donc facile pour moi).

Cependant, j'ai remarqué quelques problèmes lors de la conversion des drapeaux de pays de Wikimedia, donc peut-être pas l'analyseur / moteur de rendu svg le plus robuste.

Adam Kerz
la source
5

Une petite extension sur la réponse 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
la source
J'ai utilisé l'extraction de largeur et de hauteur svg. Je ne suis pas sûr de la norme svg, mais dans certains de mes fichiers svg, la largeur ou la hauteur étaient suivies d'une chaîne non numérique telle que «mm» ou «px» (ex: «250 mm»). L'int ('250mm') jette une exception et j'ai dû faire quelques ajustements supplémentaires.
WigglyWorld
5

Je n'ai trouvé aucune des réponses satisfaisantes. Toutes les bibliothèques mentionnées ont un problème ou un autre comme Cairo abandonnant le support de python 3.6 (ils ont abandonné le support de Python 2 il y a environ 3 ans!). De plus, l'installation des bibliothèques mentionnées sur le Mac était une douleur.

Enfin, j'ai trouvé que la meilleure solution était svglib + reportlab . Les deux installés sans accroc à l'aide de pip et le premier appel pour convertir de svg en png ont parfaitement fonctionné! Très content de la solution.

Seulement 2 commandes font l'affaire:

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

Y a-t-il des limitations avec ceux-ci dont je devrais être conscient?

Sarang
la source
Oui, la fin du marqueur n'est pas prise en charge et ne le sera pas, github.com/deeplook/svglib/issues/177
Rainald62
2

Mise à l'échelle SVG et rendu PNG

Utilisation de pycairo et librsvg j'ai pu réaliser une mise à l'échelle et un rendu SVG en bitmap. En supposant que votre SVG ne soit pas exactement 256x256 pixels, la sortie souhaitée, vous pouvez lire le SVG dans un contexte du Caire en utilisant rsvg, puis le mettre à l'échelle et écrire au format 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')

Liaison RSVG C

Sur le site Web de Cario avec quelques modifications mineures. Aussi un bon exemple de la façon d'appeler une bibliothèque C à partir de 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
la source
Merci pour cela, cela s'est avéré très utile dans un de mes projets. Bien que Handle.get_dimension_datacela ne fonctionne pas pour moi. J'ai dû le remplacer par une simple récupération de self.props.widthet self.props.height. J'ai d'abord essayé de définir la RsvgDimensionDatastructure comme décrit sur le site Web du Caire, mais sans succès.
JeanOlivier
J'essaye d'utiliser ceci dans un de mes projets. Comment obtenir les fichiers dll requis?
Andoo
0

Voici une approche où Inkscape est appelé par Python.

Notez qu'il supprime certaines sorties cruelles qu'Inkscape écrit sur la console (en particulier, stderr et stdout) pendant un fonctionnement normal sans erreur. La sortie est capturée dans deux variables de chaîne, outet 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 '?')  )

Par exemple, lors de l'exécution d'une tâche particulière sur mon système Mac OS, j'ai outfini par être:

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

(Le fichier svg d'entrée avait une taille de 339 par 339 pixels.)

Oreiller en fer
la source
Ce n'est pas du Python de bout en bout si vous comptez sur Inkscape.
Joel du
1
@Joel: La première ligne a été modifiée pour surmonter votre objection. Mais bien sûr, même une solution Python "pure" repose sur des éléments extérieurs au langage de base, et est finalement exécutée avec le langage machine, donc peut-être qu'il n'y a rien de tel que de bout en bout!
Oreiller en fer
0

En fait, je ne voulais pas dépendre d'autre chose que de Python (Cairo, Ink .., etc.) Mes exigences devaient être aussi simples que possible, au plus, un simple pip install "savior" suffirait, c'est pourquoi aucun de ceux ci-dessus ne le faisait. t convient pour moi.

Je suis passé par là (en allant plus loin que Stackoverflow sur la recherche). https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/

Ça a l'air bien, jusqu'à présent. Alors je le partage au cas où quelqu'un se trouverait dans la même situation.

Ualter Jr.
la source