In a pyGame application, I would like to render resolution-free GUI widgets described in SVG.

How can I achieve this?

(I like the OCEMP GUI toolkit but it seems to be bitmap dependent for its rendering)

This is a complete example which combines hints by other people here.
It should render a file called test.svg from the current directory. It was tested on Ubuntu 10.10, python-cairo 1.8.8, python-pygame 1.9.1, python-rsvg 2.30.0.

#!/usr/bin/python

import array
import math

import cairo
import pygame
import rsvg

WIDTH = 512
HEIGHT = 512

data = array.array('c', chr(0) * WIDTH * HEIGHT * 4)
surface = cairo.ImageSurface.create_for_data(
    data, cairo.FORMAT_ARGB32, WIDTH, HEIGHT, WIDTH * 4)

pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
svg = rsvg.Handle(file="test.svg")
ctx = cairo.Context(surface)
svg.render_cairo(ctx)

screen = pygame.display.get_surface()
image = pygame.image.frombuffer(data.tostring(), (WIDTH, HEIGHT),"ARGB")
screen.blit(image, (0, 0)) 
pygame.display.flip() 

clock = pygame.time.Clock()
while True:
    clock.tick(15)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            raise SystemExit

The question is quite old but 10 years passed and there is new possibility that works and does not require librsvg anymore. There is Cython wrapper over nanosvg library and it works:

from svg import Parser, Rasterizer


def load_svg(filename, surface, position, size=None):
    if size is None:
        w = surface.get_width()
        h = surface.get_height()
    else:
        w, h = size
    svg = Parser.parse_file(filename)
    rast = Rasterizer()
    buff = rast.rasterize(svg, w, h)
    image = pygame.image.frombuffer(buff, (w, h), 'ARGB')
    surface.blit(image, position)

I found Cairo/rsvg solution too complicated to get to work because of dependencies are quite obscure to install.

You can use Cairo (with PyCairo), which has support for rendering SVGs. The PyGame webpage has a HOWTO for rendering into a buffer with a Cairo, and using that buffer directly with PyGame.

SVG files are supported with Pygame Version 2.0. Since Version 2.0.2, SDL Image supports SVG (Scalable Vector Graphics) files (see SDL_image 2.0). Therefore, with pygame version 2.0.1, SVG files can be loaded into a pygame.Surface object with pygame.image.load():

surface = pygame.image.load('my.svg')

Before Pygame 2, you had to implement Scalable Vector Graphics loading with other libraries. Below are some ideas on how to do this.


A very simple solution is to use CairoSVG. With the function cairosvg.svg2png, an Vector Graphics (SVG) files can be directly converted to an [Portable Network Graphics (PNG)] file

Install CairoSVG.

pip install CairoSVG

Write a function that converts a SVF file to a PNG (ByteIO) and creates a pygame.Surface object may look as follows:

import cairosvg
import io

def load_svg(filename):
    new_bites = cairosvg.svg2png(url = filename)
    byte_io = io.BytesIO(new_bites)
    return pygame.image.load(byte_io)

See also Load SVG


An alternative is to use svglib. However, there seems to be a problem with transparent backgrounds. There is an issue about this topic How to make the png background transparent? #171.

Install svglib.

pip install svglib

A function that parses and rasterizes an SVG file and creates a pygame.Surface object may look as follows:

from svglib.svglib import svg2rlg
import io

def load_svg(filename):
    drawing = svg2rlg(filename)
    str = drawing.asString("png")
    byte_io = io.BytesIO(str)
    return pygame.image.load(byte_io)

Anther simple solution is to use pynanosvg. The downside of this solution is that nanosvg is no longer actively supported and does not work with Python 3.9. pynanosvg can be used to load and rasterize Vector Graphics (SVG) files. Install Cython and pynanosvg:

pip install Cython
pip install pynanosvg

The SVG file can be read, rasterized and loaded into a pygame.Surface object with the following function:

from svg import Parser, Rasterizer

def load_svg(filename, scale=None, size=None, clip_from=None, fit_to=None, foramt="RGBA"):
    svg = Parser.parse_file(filename)
    scale = min((fit_to[0] / svg.width, fit_to[1] / svg.height)
                if fit_to else ([scale if scale else 1] * 2))
    width, height = size if size else (svg.width, svg.height)
    surf_size = round(width * scale), round(height * scale)
    buffer = Rasterizer().rasterize(svg, *surf_size, scale, *(clip_from if clip_from else 0, 0))
    return  pygame.image.frombuffer(buffer, surf_size, foramt)

Minimal example:

import cairosvg
import pygame
import io

def load_svg(filename):
    new_bites = cairosvg.svg2png(url = filename)
    byte_io = io.BytesIO(new_bites)
    return pygame.image.load(byte_io)

pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

pygame_surface = load_svg('Ice-001.svg')
size = pygame_surface.get_size()
scale = min(window.get_width() / size[0], window.get_width() / size[1]) * 0.8
pygame_surface = pygame.transform.scale(pygame_surface, (round(size[0] * scale), round(size[1] * scale)))

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    window.fill((127, 127, 127))
    window.blit(pygame_surface, pygame_surface.get_rect(center = window.get_rect().center))
    pygame.display.flip()

pygame.quit()
exit()

I realise this doesn’t exactly answer your question, but there’s a library called Squirtle that will render SVG files using either Pyglet or PyOpenGL.

Cairo cannot render SVG out of the box.
It seems we have to use librsvg.

Just found those two pages:

Something like this should probably work (render test.svg to test.png):

import cairo
import rsvg

WIDTH, HEIGHT  = 256, 256
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)

ctx = cairo.Context (surface)

svg = rsvg.Handle(file="test.svg")
svg.render_cairo(ctx)

surface.write_to_png("test.png")

pygamesvg seems to do what you want (though I haven’t tried it).

The last comment crashed when I ran it because svg.render_cairo() is expecting a cairo context and not a cairo surface. I created and tested the following function and it seems to run fine on my system.

import array,cairo, pygame,rsvg

def loadsvg(filename,surface,position):
    WIDTH = surface.get_width()
    HEIGHT = surface.get_height()
    data = array.array('c', chr(0) * WIDTH * HEIGHT * 4)
    cairosurface = cairo.ImageSurface.create_for_data(data, cairo.FORMAT_ARGB32, WIDTH, HEIGHT, WIDTH * 4)
    svg = rsvg.Handle(filename)
    svg.render_cairo(cairo.Context(cairosurface))
    image = pygame.image.frombuffer(data.tostring(), (WIDTH, HEIGHT),"ARGB")
    surface.blit(image, position) 

WIDTH = 800
HEIGHT = 600
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
screen = pygame.display.get_surface()

loadsvg("test.svg",screen,(0,0))

pygame.display.flip() 

clock = pygame.time.Clock()
while True:
    clock.tick(15)
    event = pygame.event.get()
    for e in event:
        if e.type == 12:
            raise SystemExit

Based on other answers, here’s a function to read a SVG file into a pygame image – including correcting color channel order and scaling:

def pygame_svg( svg_file, scale=1 ):
    svg = rsvg.Handle(file=svg_file)
    width, height= map(svg.get_property, ("width", "height"))
    width*=scale; height*=scale
    data = array.array('c', chr(0) * width * height * 4)
    surface = cairo.ImageSurface.create_for_data( data, cairo.FORMAT_ARGB32, width, height, width*4)
    ctx = cairo.Context(surface)
    ctx.scale(scale, scale)
    svg.render_cairo(ctx)

    #seemingly, cairo and pygame expect channels in a different order...
    #if colors/alpha are funny, mess with the next lines
    import numpy
    data= numpy.fromstring(data, dtype="uint8")
    data.shape= (height, width, 4)
    c= data.copy()
    data[::,::,0]=c[::,::,1]
    data[::,::,1]=c[::,::,0]
    data[::,::,2]=c[::,::,3]
    data[::,::,3]=c[::,::,2]

    image = pygame.image.frombuffer(data.tostring(), (width, height),"ARGB")
    return image