在 Python 中将 SVG 转换为 PNG

如何在 Python 中将 svg转换为 png?我将 svg存储在 StringIO的一个实例中。我应该使用 pyCairo 库吗?我该怎么写代码?

166815 次浏览

答案是“ < strong > pysvg ”——针对 Librsvg的 Python 绑定。

有一个 Ubuntu Python-rsvg 包提供它。在 Google 上搜索它的名字是很糟糕的,因为它的源代码似乎包含在 Gnome 项目 GIT 存储库中。

我创建了一个极简主义的“ hello world”,它将 SVG 呈现给 cairo 并将其写入磁盘:

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

更新 : 截至2014年,Fedora Linux 发行版所需的软件包是: gnome-python2-rsvg

试试这个: http://cairosvg.org/

该网站表示:

CairoSVG 是用纯 Python 编写的,并且只依赖于 Pycairo 已知用于 Python 2.6和2.7。

更新 November 25,20162016年11月25日:

0是一个新的主要版本,它的变更日志包括:

  • 放弃对 Python 2的支持

安装 Inkscape 并调用它作为命令行:

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

你也可以只使用参数 -j拍摄特定的矩形区域,例如坐标“0:125:451:217”

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

如果只想在 SVG 文件中显示一个对象,可以使用在 SVG 中设置的对象 ID 指定参数 -i。它隐藏了其他一切。

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

下面是我使用 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')

而且非常有效!

详见: Cairosvg 文件

我正在使用 魔杖(ImageMagick 周围的 Wand 包装器的一个实现)来导入一些相当高级的 SVG,到目前为止已经看到了很好的结果!这就是它所需要的全部代码:

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

我今天刚刚发现了这个问题,我觉得这个问题值得分享给其他人,因为这些问题已经有一段时间没有得到回答了。

注意: 从技术上讲,在测试中,我发现实际上甚至不需要传递 ImageMagick 的 format 参数,所以 with wand.image.Image( blob=svg_file.read() ) as image:是真正需要的。

编辑: 从 qris 的编辑尝试,这里有一些有用的代码,可以让您使用 ImageMagick 和一个具有透明背景的 SVG:

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)

我刚刚在这里找到的另一个解决方案

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 很容易从 Windows 中的二进制包安装(我用它做其他事情,所以对我来说很容易)。

但是,在从 Wikimedia 转换国家标志时,我注意到了一些问题,所以可能不是最健壮的 svg 解析器/渲染器。

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

下面是 Python 调用 Inkscape 的方法。

注意,它抑制了 Inkscape 在正常无错操作期间写入控制台(特别是 stderr 和 stdout)的某些错误输出。输出在两个字符串变量 outerr中捕获。

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 '?')  )

例如,当在我的 Mac OS 系统上运行一个特定的作业时,out最终是:

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

(输入 svg 文件的大小为339乘339像素。)

SVG 缩放和 PNG 渲染

使用 皮开罗Librsvg我能够实现 SVG 的缩放和渲染到一个位图。假设您的 SVG 不是理想的输出(256x256像素) ,那么您可以使用 rsvg 将 SVG 读取到一个開罗上下文,然后对其进行缩放并写入 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')

RSVG C 绑定

在 Cario 网站上做了一些小小的修改,也是一个很好的例子来说明如何从 Python 中调用一个 C-library

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)

我没有找到任何令人满意的答案。上面提到的所有库都存在一些问题,或者其他一些问题,例如,Cairo 放弃了对 Python 3.6的支持(他们在大约3年前就放弃了对 Python 2的支持!).此外,在 Mac 上安装上述库也是一件痛苦的事情。

最后,我发现最好的解决方案是 Svglib + 报告实验室。这两个安装没有一个顺利使用 pip 和第一次调用转换为 svg 到 png 工作得非常漂亮!对解决方案非常满意。

只需要两个命令就可以完成这个任务:

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

有什么我需要注意的限制吗?

事实上,我不想依赖任何东西,除了 Python (开罗,墨水,等等) 我的要求是尽可能简单,最多,一个简单的 pip install "savior"就足够了,这就是为什么上述任何一个都不适合我。

我完成了这项研究(比 Stackoverflow 的研究更进一步)。 Https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/

到目前为止看起来还不错,所以我和大家分享一下,以防有人遇到同样的情况。

下面是另一个不使用 rsvg 的解决方案(当前在 windows 中不可用)。只能使用 pip install CairoSVG安装 cairosvg

Svg2png.py

from cairosvg import svg2png
svg_code = open("input.svg", 'rt').read()
svg2png(bytestring=svg_code,write_to='output.png')

试试这个 python 脚本:

别忘了安装 cairosvg: pip3 install cairosvg

#!/usr/bin/env python3
import os
import cairosvg


for file in os.listdir('.'):
if os.path.isfile(file) and file.endswith(".svg"):
name = file.split('.svg')[0]
cairosvg.svg2png(url=name+'.svg',write_to=name+'.png')


尝试使用 Gtk.Image 和 Gdk.Pixbuf

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')


from gi.repository import Gdk, Gtk
from PIL import Image


image = Gtk.Image()
image.set_from_file("path/to/image.svg")
pb = image.get_pixbuf()
pb.savev("path/to/convented/image.jpeg","jpeg",[],[])
im = Image.open("path/to/convented/image.jpeg")
pix = im.load()
print(pix[1,1])

这里的所有答案都很棒,但是我想提一下,我已经创建了一个简单的库,将 SVG 的文件作为枕头图像实例加载,然后可以导出这些实例。它像 blj 的回答一样使用 inkscape,但是呈现为 stdout,这样就不会产生临时文件。README 中有一些基本的用法。

Https://github.com/jlwoolf/pillow-svg

编辑:
正如所建议的,这里有一个简短的解释,因为链接可能会变得无效:

该库使用 inkscape 的命令行界面,使用 python 子进程库将图像转换为特定大小的 png 或 dpi。通过将 --export-filename设置为 -,inkscape 将输出重定向到 stdout。丢弃前两行,剩下的输出传递给 PIL.Image.open,将其转换为枕头图像实例。

import subprocess
from PIL import Image


options = ["inkscape", "--export-filename=-", "--export-type=png", "file.svg"]


pipe = subprocess.Popen(options, stdout=subprocess.PIPE)


pipe.stdout.readline()
pipe.stdout.readline()


img = Image.open(pipe.stdout)

从那里你可以做任何枕头图像操作你需要(像导出为一个 jpg,调整大小,裁剪等)。

编辑2:
刚刚增加了对 滑雪蟒的支持(还没有完全测试,但目前看来是可行的)。这样,只需要安装一个 pip 就可以将 svg 转换为 png (不需要使用 inkscape)。

下面是图书馆如何使用 skia-python 的解释:

首先,将 svg 文件加载到 skia.SVGDOM中。从那里您可以使用 containerSize获取 SVGDOM 的维度。然后生成所需图像输出大小的 skia.Surface。对画布进行缩放以使 svg 与表面相匹配,然后呈现 svg。从那里,可以制作一个图像快照,然后将其反馈给 PIL.Image.open

import skia
from PIL import Image


skia_stream = skia.Stream.MakeFromFile("file.svg")
skia_svg = skia.SVGDOM.MakeFromStream(skia_stream)


svg_width, svg_height = skia_svg.containerSize()
surface_width, surface_height = 512, 512


surface = skia.Surface(surface_width, surface_height)
with surface as canvas:
canvas.scale(surface_width / svg_width, surface_height / svg_height)
skia_svg.render(canvas)


with io.BytesIO(surface.makeImageSnapshot().encodeToData()) as f:
img = Image.open(f)
img.load()

编辑3:
我使图书馆更加丰富多彩了。现在有一个命令行实用程序可以方便地进行 svg 转换,同时还有更多说明使用方法的文档。希望能有帮助!

这个 StackOverflow 的答案发布我的代码。这是 svglib + reportlib不支持透明背景和无缩放的解决方案(参见 @ 沙郎的回答@ ualter-jr 的回答以及 缩放不起作用这次是关于透明度的上的 Github 问题)

这使用 pyMuPDF来呈现从 reportlab到 PNG 的中间 pdf。

很大的优势是因为 没有需要任何外部库,因为 pymupdf预先为 Windows、 Linux 和 MacOS 编译了轮子。

整件事就像

pip install pymupdf svglib

然后执行以下代码行

import fitz
from svglib import svglib
from reportlab.graphics import renderPDF


# Convert svg to pdf in memory with svglib+reportlab
# directly rendering to png does not support transparency nor scaling
drawing = svglib.svg2rlg(path="input.svg")
pdf = renderPDF.drawToString(drawing)


# Open pdf with fitz (pyMuPdf) to convert to PNG
doc = fitz.Document(stream=pdf)
pix = doc.load_page(0).get_pixmap(alpha=True, dpi=300)
pix.save("output.png")