Konversi SVG ke PNG dengan Python

99

Bagaimana saya mengkonversi svgke png, di Python? Saya menyimpan svgdalam sebuah contoh StringIO. Haruskah saya menggunakan pustaka pyCairo? Bagaimana cara menulis kode itu?

ram1
sumber
3
Mungkin duplikat stackoverflow.com/questions/2932408/…
Optimal Cynic
2
Utas itu membuat masalah tidak terpecahkan. Jawaban yang diterima datang dari penanya yang membagikan percobaan kode yang gagal. Jawaban lain menyarankan ImageMagick tetapi seorang komentator mengatakan ImageMagick melakukan "pekerjaan yang mengerikan dalam menafsirkan SVG." Saya tidak ingin gambar png saya terlihat mengerikan, jadi saya mengajukan pertanyaan kembali.
ram1
Contoh di tautan itu khusus untuk Win32. Saya menjalankan linux.
ram1
Lihatlah posting blog ini , sepertinya itu yang Anda butuhkan.
giodamelio

Jawaban:

60

Jawabannya adalah " pyrsvg " - pengikatan Python untuk librsvg .

Ada paket Ubuntu python-rsvg yang menyediakannya. Mencari namanya di Google buruk karena kode sumbernya tampaknya terdapat di dalam repositori GIT proyek Gnome "gnome-python-desktop".

Saya membuat "hello world" minimalis yang merender SVG ke permukaan Kairo dan menulisnya ke disk:

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

Update : sebagai 2014 paket yang diperlukan untuk distribusi Fedora Linux adalah: gnome-python2-rsvg. Daftar potongan di atas masih berfungsi apa adanya.

jsbueno
sumber
1
Bagus, berfungsi dengan baik. Tetapi apakah ada cara untuk cairomenentukan TINGGI dan LEBAR gambar itu sendiri? Saya telah melihat ke dalam *.svgfile tersebut, untuk mengekstrak HEIGHT dan WIDTH dari sana, tetapi keduanya diatur ke 100%. Tentu saja, saya dapat melihat properti gambar, tetapi karena ini hanya satu langkah dalam pemrosesan gambar, ini bukan yang saya inginkan.
quapka
1
Jika "lebar" dan "tinggi" file Anda disetel ke 100%, tidak ada keajaiban yang dapat dilakukan Kairo atau rsvg untuk menebak ukurannya: file SVG seperti itu dibiarkan berukuran independen oleh perangkat lunak pembuat (/ orang). Kode HTML di sekitarnya untuk mengimpor file SVG akan memberikan ukuran fisik. Namun, objek "Handle" dari rsvg memiliki .get_dimension_data()metode yang bekerja untuk file contoh saya (SVG yang berperilaku baik) - cobalah.
jsbueno
1
per 2014, untuk ubuntu, Anda dapat menggunakan: apt-get install python-rsvg
t1m0
1
Apakah ada perintah cepat untuk menambahkan latar belakang putih ke gambar jika latar belakangnya sekarang transparan?
fiatjaf
@jsbueno Saya menggunakan Windows 8.1 dan python 2.7.11 Bagaimana cara menginstal cairo dan rsvg dan membuatnya bekerja. Saya berjuang untuk membuat ini berhasil. BTW +1 untuk penjelasan rinci Anda.
Marlon Abeykoon
88

Inilah yang saya lakukan menggunakan 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')

Dan itu bekerja seperti pesona!

Lihat lebih lanjut: dokumen cairosvg

nemesisfixx
sumber
5
Hai. Tahukah Anda bagaimana saya bisa melakukan hal yang sama tetapi tanpa menulis ke file? Saya perlu mendorong konten png ke browser dari server web, sehingga pengguna dapat mengunduh gambar. Menyimpan file png bukanlah opsi yang valid dalam proyek kami, itulah mengapa saya membutuhkannya seperti itu. Terima kasih
estemendoza
4
Saya telah melakukan ini sendiri. Ini pada dasarnya tergantung pada alat atau kerangka kerja apa yang Anda miliki saat menangani permintaan web Anda, tetapi tidak peduli apa itu, ide dasarnya adalah svg2pngmengambil streamobjek di write_toparameter, dan ini bisa menjadi objek Respons HTTP Anda (yang kebanyakan kerangka kerja adalah objek seperti file) atau aliran lain, yang kemudian Anda tayangkan ke browser menggunakan Content-Dispositiontajuk. lihat di sini: stackoverflow.com/questions/1012437/…
nemesisfixx
4
Bagi mereka yang mengalami masalah dengan kode itu, seperti saya: 1). bytestringmenerima byte, jadi konversikan string terlebih dahulu dengan bytestring=bytes(svg,'UTF-8') 2). mode file harus biner, jadiopen('output.png','wb')
Serj Zaharchenko
3
cairosvg hanya mendukung Python 3.4+. Mereka telah menghentikan dukungan Python 2
Marlon Abeykoon
1
Tidak ada svg2pnguntuk saya, saya harus menggunakan cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png').
tobltobs
39

Instal Inkscape dan sebut sebagai baris perintah:

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

Anda juga dapat mengambil area persegi panjang tertentu hanya menggunakan parameter -j, misalnya koordinat "0: 125: 451: 217"

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

Jika Anda hanya ingin menampilkan satu objek di file SVG, Anda dapat menentukan parameter -idengan id objek yang telah Anda siapkan di SVG. Itu menyembunyikan yang lainnya.

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -i ${object} -j -a ${coordinates} -e ${dest_png}
blj
sumber
2
1 karena ini juga sangat berguna untuk skrip shell. Lihat inkscape.org/doc/inkscape-man.html untuk dokumen lengkap tentang baris perintah Inkscape.
Perdana
Terima kasih, ini adalah cara termudah yang saya temukan untuk melakukan ini. Di Windows, agar Anda tidak perlu mengetikkan path lengkap ke Inkscape setiap saat, Anda bisa menambahkannya ke Path di Variabel Lingkungan .
Alex S
3
Ini bukanlah jawaban. Ini solusinya. OP meminta solusi Python.
lamino
31

Saya menggunakan Wand-py (implementasi pembungkus Wand di sekitar ImageMagick) untuk mengimpor beberapa SVG yang cukup canggih dan sejauh ini telah melihat hasil yang bagus! Ini semua kode yang dibutuhkan:

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

Saya baru saja menemukan ini hari ini, dan merasa layak untuk dibagikan kepada siapa pun yang mungkin kesulitan menemukan jawaban ini karena sudah lama sejak sebagian besar pertanyaan ini dijawab.

CATATAN: Secara teknis dalam pengujian saya menemukan Anda bahkan tidak benar-benar harus mengirimkan parameter format untuk ImageMagick, jadi with wand.image.Image( blob=svg_file.read() ) as image:hanya itu yang benar-benar dibutuhkan.

EDIT: Dari percobaan pengeditan oleh qris, berikut beberapa kode berguna yang memungkinkan Anda menggunakan ImageMagick dengan SVG yang memiliki latar belakang transparan:

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
sumber
2
Wand bekerja jauh lebih baik daripada Kairo untuk PNG saya.
qris
3
Saya mendapatkan kesalahan image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
adrienlucca.wordpress.com
2
svg_filediasumsikan sebagai objek "file" dalam contoh ini, pengaturan svg_fileakan terlihat seperti:svg_file = File.open(file_name, "r")
streetlogics
2
Terima kasih, cairodan rsvgmetode 'diterima' tidak berfungsi untuk PDF saya. pip install wanddan cuplikan Anda berhasil;)
nmz787
5
Jika Anda memiliki svg strmaka Anda harus terlebih dahulu encode ke biner seperti ini: svg_blob = svg_str.encode('utf-8'). Sekarang Anda dapat menggunakan metode di atas dengan mengganti blob=svg_file.read()dengan blob=svg_blob.
asherbar
12

Coba ini: http://cairosvg.org/

Situs itu mengatakan:

CairoSVG ditulis dengan python murni dan hanya bergantung pada Pycairo. Ia dikenal bekerja pada Python 2.6 dan 2.7.

Pembaruan 25 November 2016 :

2.0.0 adalah versi mayor baru, log perubahannya meliputi:

  • Jatuhkan dukungan Python 2
pengguna732592
sumber
Sayangnya, ada dua masalah dengan ini. Pertama, itu tidak menangani file <clipPath><rect ... /></clipPath>. Kedua, itu tidak mengambil opsi -d (DPI).
Ray
2
@Ray, kirimkan laporan bug / permintaan fitur pada pelacak CairoSVG !
Simon Sapin
@Simon, Bisakah kamu melakukannya? Saya terlalu sibuk dan saya akan berada dalam 1-2 bulan ke depan.
Ray
2
@Ray, sebenarnya opsi -d / --dpi telah ada untuk beberapa waktu sekarang, dan saya diberitahu bahwa dukungan untuk <clippath> telah ditambahkan beberapa minggu yang lalu dalam versi git.
Simon Sapin
@Son, bagus! Terima kasih! :)
Ray
6

Solusi lain yang baru saya temukan di sini Bagaimana cara merender SVG skala ke 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 mudah diinstal dari paket biner di Windows (dan saya menggunakannya untuk hal lain, jadi mudah bagi saya).

Namun, saya melihat beberapa masalah saat mengonversi bendera negara dari Wikimedia, jadi mungkin bukan pengurai / penyaji svg yang paling kuat.

Adam Kerz
sumber
5

Sedikit tambahan untuk jawaban 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
sumber
Saya menggunakan ekstraksi lebar dan tinggi svg. Saya tidak yakin tentang standar svg tetapi di beberapa file svg saya lebar atau tinggi diikuti oleh string non numerik seperti 'mm' atau 'px' (misal: '250mm'). Int ('250mm') memberikan pengecualian dan saya harus membuat beberapa penyesuaian tambahan.
WigglyWorld
3

Saya tidak menemukan jawaban yang memuaskan. Semua pustaka yang disebutkan memiliki beberapa masalah atau yang lainnya seperti Kairo yang menjatuhkan dukungan untuk python 3.6 (mereka menjatuhkan dukungan Python 2 sekitar 3 tahun yang lalu!). Juga, menginstal pustaka yang disebutkan di Mac sangat menyebalkan.

Akhirnya, saya menemukan solusi terbaik adalah svglib + reportlab . Keduanya diinstal tanpa hambatan menggunakan pip dan panggilan pertama untuk mengonversi dari svg ke png bekerja dengan baik! Sangat senang dengan solusinya.

Hanya 2 perintah yang melakukan trik:

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

Apakah ada batasan dengan ini yang harus saya waspadai?

Sarang
sumber
Ya, marker-end tidak didukung, dan tidak akan tersedia, github.com/deeplook/svglib/issues/177
Rainald62
2

Penskalaan SVG dan rendering PNG

Menggunakan pycairo dan librsvg saya bisa mencapai penskalaan SVG dan rendering ke bitmap. Dengan asumsi SVG Anda tidak persis 256x256 piksel, keluaran yang diinginkan, Anda dapat membaca dalam konteks SVG ke Kairo menggunakan rsvg dan kemudian menskalakannya dan menulis ke 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')

Pengikatan RSVG C.

Dari situs Cario dengan sedikit modifikasi. Juga contoh yang baik tentang bagaimana memanggil pustaka-C dari 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
sumber
Terima kasih untuk ini, ini terbukti sangat berguna dalam proyek saya. Meskipun Handle.get_dimension_datatidak berhasil untuk saya. Saya harus menggantinya dengan pengambilan sederhana dari self.props.widthdan self.props.height. Saya pertama kali mencoba mendefinisikan RsvgDimensionDataStruktur seperti yang dijelaskan di situs web Kairo, tetapi tidak berhasil.
JeanOlivier
Saya mencoba menggunakan ini dalam proyek saya. Bagaimana cara mendapatkan file dll yang diperlukan?
Andoo
0

Berikut adalah pendekatan dimana Inkscape dipanggil oleh Python.

Perhatikan bahwa ini menekan output crufty tertentu yang ditulis Inkscape ke konsol (khususnya, stderr dan stdout) selama operasi normal bebas kesalahan. Outputnya ditangkap dalam dua variabel string, outdan 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 '?')  )

Misalnya, saat menjalankan pekerjaan tertentu di sistem Mac OS saya, outakhirnya menjadi:

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

(File svg masukan berukuran 339 kali 339 piksel.)

Bantal Besi
sumber
Ini bukan Python ujung ke ujung jika Anda mengandalkan Inkscape.
Joel
1
@ Joel: Baris pertama telah dimodifikasi untuk mengatasi keberatan Anda. Tetapi tentu saja, bahkan solusi Python "murni" bergantung pada elemen di luar bahasa inti, dan pada akhirnya dijalankan dengan bahasa mesin, jadi mungkin tidak ada yang namanya end-to-end!
Bantal Besi
0

Sebenarnya, saya tidak ingin bergantung pada apa pun selain Python (Kairo, Ink .., dll.) Persyaratan saya adalah sesederhana mungkin, paling-paling, yang sederhana pip install "savior"sudah cukup, itulah mengapa yang di atas tidak ' t cocok untukku.

Saya berhasil melewati ini (melangkah lebih jauh dari Stackoverflow dalam penelitian). https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/

Tampak bagus, sejauh ini. Jadi saya membagikannya jika ada yang berada dalam situasi yang sama.

Ualter Jr.
sumber