Jak przekonwertować plik svg
na png
w w Pythonie? Przechowuję svg
w instancji StringIO
. Czy powinienem korzystać z biblioteki pyCairo? Jak napisać ten kod?
Jak przekonwertować plik svg
na png
w w Pythonie? Przechowuję svg
w instancji StringIO
. Czy powinienem korzystać z biblioteki pyCairo? Jak napisać ten kod?
Odpowiedzi:
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.
cairo
określić WYSOKOŚĆ i SZEROKOŚĆ obrazu? Zajrzałem do *.svg
pliku, 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ę.
.get_dimension_data()
metodę, która działała dla mojego przykładowego pliku (dobrze zachowany SVG) - spróbuj.
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
svg2png
przyjmuje stream
obiekt w write_to
parametrze 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-Disposition
nagłówka. patrz tutaj: stackoverflow.com/questions/1012437/…
bytestring
akceptuje 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')
svg2png
dla mnie, musiałem użyć cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png')
.
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 -i
z 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}
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)
image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
svg_file
zakłada się, że jest to obiekt „plik” w tym przykładzie, ustawienie svg_file
będzie wyglądać następująco:svg_file = File.open(file_name, "r")
cairo
i rsvg
„przyjęty” metoda nie działa na moim PDF. pip install wand
i twój fragment
str
potem 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
.
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
<clipPath><rect ... /></clipPath>
. Po drugie, nie przyjmuje opcji -d (DPI).
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.
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)
Ż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ć?
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.
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')
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)
Handle.get_dimension_data
nie działa dla mnie. Musiałem go zastąpić prostym pobieraniem self.props.width
i self.props.height
. Najpierw próbowałem zdefiniować RsvgDimensionData
Strukturę zgodnie z opisem na stronie internetowej Cairo, ale bez powodzenia.
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 out
i 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, out
skoń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).
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.