Konwertuj SVG na PNG w Pythonie

99

Jak przekonwertować plik svgna pngw w Pythonie? Przechowuję svgw instancji StringIO. Czy powinienem korzystać z biblioteki pyCairo? Jak napisać ten kod?

ram1
źródło
3
Prawdopodobnie duplikat stackoverflow.com/questions/2932408/…
Optimal Cynic,
2
Ten wątek nie rozwiązał problemu. Zaakceptowana odpowiedź pochodziła od pytającego, który udostępnił swoją nieudaną próbę kodu. Inna odpowiedź sugerowała ImageMagick, ale komentator powiedział, że ImageMagick wykonuje „okropną robotę interpretacji SVG”. Nie chcę, aby moje pliki PNG wyglądały okropnie, więc ponownie zadaję pytanie.
ram1
1
Wypróbuj cairographics.org/cookbook/librsvgpython
Optimal Cynic.
Przykłady w tym łączu są specyficzne dla Win32. Używam Linuksa.
ram1
Spójrz na ten post na blogu, wygląda na to, że może być to, czego potrzebujesz.
giodamelio

Odpowiedzi:

60

Odpowiedzią jest „ pyrsvg ” - powiązanie Pythona z librsvg .

Dostarcza go pakiet Ubuntu python-rsvg . Wyszukiwanie w Google jego nazwy jest słabe, ponieważ jego kod źródłowy wydaje się być zawarty w repozytorium GIT projektu Gnome „gnome-python-desktop”.

Zrobiłem minimalistyczny „hello world”, który renderuje SVG na powierzchni kairu i zapisuje na dysku:

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

Aktualizacja : od 2014 r potrzebnej pakiet dla dystrybucji Fedora Linux jest: gnome-python2-rsvg. Powyższa lista fragmentów nadal działa tak, jak jest.

jsbueno
źródło
1
Świetnie, ładnie działa. Ale czy istnieje sposób, aby samodzielnie cairookreślić WYSOKOŚĆ i SZEROKOŚĆ obrazu? Zajrzałem do *.svgpliku, aby wyodrębnić stamtąd HEIGHT i WIDTH, ale oba są ustawione na 100%. Oczywiście mogę przyjrzeć się właściwościom obrazu, ale ponieważ jest to tylko jeden krok w przetwarzaniu obrazu, tego nie chcę.
quapka
1
Jeśli "szerokość" i "wysokość" twoich plików są ustawione na 100%, nie ma magicznego Kairu ani rsvg, który mógłby odgadnąć rozmiar: takie pliki SVG pozostawały niezależne od rozmiaru przez oprogramowanie twórcy (/ osobę). Otaczający kod HTML do zaimportowania pliku SVG zapewniłby rozmiar fizyczny. Jednak obiekt "Handle" rsvg ma .get_dimension_data()metodę, która działała dla mojego przykładowego pliku (dobrze zachowany SVG) - spróbuj.
jsbueno
1
od 2014 r. dla ubuntu można użyć: apt-get install python-
rsvg
1
Czy istnieje szybkie polecenie dodania białego tła do obrazu, jeśli jego bieżące tło jest przezroczyste?
fiatjaf
@jsbueno Używam Windows 8.1 i Pythona 2.7.11 Jak mogę zainstalować cairo i rsvg i sprawić, by to działało. Walczyłem, żeby to zadziałało. BTW +1 za szczegółowe wyjaśnienie.
Marlon Abeykoon
88

Oto, co zrobiłem, używając 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')

I działa jak urok!

Zobacz więcej: dokument cairosvg

nemesisfixx
źródło
5
Cześć. Czy wiesz, jak mogę zrobić to samo, ale bez zapisywania do pliku? Muszę przesłać zawartość png do przeglądarki z serwera WWW, aby użytkownik mógł pobrać obraz. Zapisywanie pliku png nie jest poprawną opcją w naszym projekcie, dlatego potrzebuję tego w ten sposób. Dzięki
estemendoza
4
Sam to robiłem. Zasadniczo zależy to od tego, jakie narzędzia lub struktury masz pod ręką podczas obsługi żądań internetowych, ale bez względu na to, co to jest, podstawową ideą jest to, że svg2pngprzyjmuje streamobiekt w write_toparametrze i może to być albo twój obiekt odpowiedzi HTTP (który w większość frameworków to obiekt podobny do pliku) lub inny strumień, który następnie podajesz przeglądarce za pomocą Content-Dispositionnagłówka. patrz tutaj: stackoverflow.com/questions/1012437/…
nemesisfixx
4
Dla tych, którzy mają problemy z tym kodem, tak jak ja: 1). bytestringakceptuje bajty, więc najpierw przekonwertuj ciąg z bytestring=bytes(svg,'UTF-8') 2). tryb plików powinien być binarny, więcopen('output.png','wb')
Serj Zaharchenko
3
cairosvg obsługuje tylko Python 3.4+.
Porzucili
1
Nie było svg2pngdla mnie, musiałem użyć cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png').
tobltobs
39

Zainstaluj Inkscape i wywołaj go jako wiersz poleceń:

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

Możesz również przyciągnąć określony prostokątny obszar tylko za pomocą parametru -j, np. Współrzędna „0: 125: 451: 217”

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

Jeśli chcesz pokazać tylko jeden obiekt w pliku SVG, możesz określić parametr -iz identyfikatorem obiektu, który ustawiłeś w pliku SVG. Ukrywa wszystko inne.

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -i ${object} -j -a ${coordinates} -e ${dest_png}
blj
źródło
2
+1, ponieważ jest to również bardzo przydatne w przypadku skryptów powłoki. Zobacz inkscape.org/doc/inkscape-man.html, aby uzyskać pełną dokumentację w wierszu poleceń Inkscape.
Prime
Dzięki, to był najłatwiejszy sposób na zrobienie tego. W systemie Windows, aby za każdym razem nie trzeba było wpisywać pełnej ścieżki do Inkscape, możesz dodać ją do swojej ścieżki w zmiennych środowiskowych .
Alex S,
3
To nie jest odpowiedź. To obejście. OP poprosił o rozwiązanie w języku Python.
lamino
31

Używam Wand-py (implementacja opakowania Wand wokół ImageMagick) do importowania całkiem zaawansowanych plików SVG i do tej pory widziałem świetne rezultaty! To jest cały kod, którego potrzebuje:

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

Właśnie dziś to odkryłem i poczułem, że warto się nim podzielić z innymi, którzy mogą natknąć się na tę odpowiedź, ponieważ minęło trochę czasu, odkąd udzielono odpowiedzi na większość tych pytań.

UWAGA: Technicznie podczas testów odkryłem, że nie musisz nawet przekazywać parametru formatu dla ImageMagick, więc with wand.image.Image( blob=svg_file.read() ) as image: było to wszystko, co było naprawdę potrzebne.

EDYCJA: Z próby edycji dokonanej przez qris, oto pomocny kod, który pozwala używać ImageMagick z plikiem SVG z przezroczystym tłem:

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
źródło
2
Różdżka działała dużo lepiej niż Kair dla moich plików PNG.
qris
3
Otrzymuję błąd image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
adrienlucca.wordpress.com
2
svg_filezakłada się, że jest to obiekt „plik” w tym przykładzie, ustawienie svg_filebędzie wyglądać następująco:svg_file = File.open(file_name, "r")
streetlogics
2
Dzięki, cairoi rsvg„przyjęty” metoda nie działa na moim PDF. pip install wandi twój fragment
załatwił sprawę
5
Jeśli masz SVG strpotem trzeba najpierw do kodowania w formacie binarnym jak ten: svg_blob = svg_str.encode('utf-8'). Teraz można użyć metody powyżej, zastępując blob=svg_file.read()z blob=svg_blob.
asherbar
12

Spróbuj tego: http://cairosvg.org/

Witryna mówi:

CairoSVG jest napisane w czystym Pythonie i zależy tylko od Pycairo. Wiadomo, że działa na Pythonie 2.6 i 2.7.

Aktualizacja 25 listopada 2016 :

2.0.0 to nowa wersja główna, jej dziennik zmian zawiera:

  • Porzuć obsługę Pythona 2
user732592
źródło
Są z tym niestety dwa problemy. Po pierwsze, nie obsługuje <clipPath><rect ... /></clipPath>. Po drugie, nie przyjmuje opcji -d (DPI).
Ray
2
@Ray, proszę przesyłać raporty o błędach / prośby o funkcje na tracker CairoSVG !
Simon Sapin
@Simon, czy możesz to zrobić, proszę? Jestem zbyt zajęty i będę w ciągu najbliższych 1-2 miesięcy.
Ray
2
@Ray, właściwie opcja -d / --dpi istnieje już od jakiegoś czasu i powiedziano mi, że obsługa <clippath> została dodana kilka tygodni temu w wersji git.
Simon Sapin
@Simon, fajnie! Dziękuję Ci! :)
Ray
6

Inne rozwiązanie, które właśnie znalazłem. Jak wyrenderować przeskalowany SVG do 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 można łatwo zainstalować z pakietu binarnego w systemie Windows (i używam go do innych rzeczy, więc jest to dla mnie łatwe).

Jednak zauważyłem kilka problemów podczas konwersji flag krajów z Wikimedia, więc być może nie jest to najbardziej niezawodny parser / renderer svg.

Adam Kerz
źródło
5

Małe rozszerzenie odpowiedzi 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
źródło
Użyłem ekstrakcji szerokości i wysokości svg. Nie jestem pewien co do standardu SVG, ale w niektórych moich plikach SVG po szerokości lub wysokości następował ciąg nienumeryczny, taki jak „mm” lub „px” (np. „250mm”). Int („250mm”) zgłasza wyjątek i musiałem wprowadzić kilka dodatkowych poprawek.
WigglyWorld
5

Żadna z odpowiedzi nie była satysfakcjonująca. Wszystkie wymienione biblioteki mają jakiś problem lub inny, jak np. W Kairze porzucenie wsparcia dla Pythona 3.6 (porzuciły obsługę Pythona 2 jakieś 3 lata temu!). Również instalacja wspomnianych bibliotek na komputerze Mac była uciążliwa.

Wreszcie znalazłem najlepszym rozwiązaniem jest svglib + reportlab . Obie instalowane bez żadnych problemów za pomocą pip i pierwszego połączenia do konwersji z SVG na PNG działały pięknie! Bardzo zadowolony z rozwiązania.

Tylko 2 polecenia załatwiają sprawę:

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

Czy są jakieś ograniczenia, o których powinienem wiedzieć?

Sarang
źródło
Tak, koniec znacznika nie jest obsługiwany i nie będzie, github.com/deeplook/svglib/issues/177
Rainald62
2

Skalowanie SVG i renderowanie PNG

Korzystanie z pycairo i librsvg udało mi się uzyskać skalowanie SVG i renderowanie do mapy bitowej. Zakładając, że twój SVG nie ma dokładnie 256x256 pikseli, pożądane wyjście, możesz wczytać w SVG do kontekstu kairskiego za pomocą rsvg, a następnie przeskalować go i zapisać do 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')

Wiązanie RSVG C.

Ze strony internetowej Cario z niewielkimi modyfikacjami. Również dobry przykład, jak wywołać bibliotekę C z Pythona

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
źródło
Dzięki za to okazał się bardzo przydatny w moim projekcie. Chociaż Handle.get_dimension_datanie działa dla mnie. Musiałem go zastąpić prostym pobieraniem self.props.widthi self.props.height. Najpierw próbowałem zdefiniować RsvgDimensionDataStrukturę zgodnie z opisem na stronie internetowej Cairo, ale bez powodzenia.
JeanOlivier
Próbuję to wykorzystać w moim projekcie. Jak uzyskać wymagane pliki DLL?
Andoo
0

Oto podejście, w którym Inkscape jest wywoływany przez Python.

Zauważ, że blokuje pewne wyjście crufty, które Inkscape zapisuje na konsoli (w szczególności stderr i stdout) podczas normalnej, wolnej od błędów operacji. Dane wyjściowe są przechwytywane w dwóch zmiennych łańcuchowych outi 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 '?')  )

Na przykład, gdy wykonałem określone zadanie w moim systemie Mac OS, outskończyło się na:

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

(Plik wejściowy SVG miał rozmiar 339 na 339 pikseli).

Żelazna poduszka
źródło
Nie jest to Python typu end-to-end, jeśli polegasz na Inkscape.
Joel
1
@Joel: Pierwsza linijka została zmodyfikowana w celu usunięcia Twojego sprzeciwu. Ale oczywiście nawet „czyste” rozwiązanie Pythona opiera się na elementach spoza języka podstawowego i ostatecznie jest uruchamiane w języku maszynowym, więc być może nie ma czegoś takiego jak kompleksowe rozwiązanie!
Iron Pillow
0

Właściwie nie chciałem być zależny od czegokolwiek innego poza Pythonem (Kair, Ink… itp.). Moje wymagania miały być tak proste, jak to tylko możliwe, co najwyżej proste pip install "savior"wystarczyłoby, dlatego żadne z powyższych nie ” t pasuje do mnie.

Przeszedłem przez to (idąc dalej niż w badaniach Stackoverflow). https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/

Jak na razie wygląda dobrze. Więc udostępniam to na wypadek, gdyby ktoś był w tej samej sytuacji.

Ualter Jr.
źródło