29

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pierre-Jean Coudert
  • 9,109
  • 10
  • 50
  • 59
  • 2
    Is there a different answer / library that applies to 2011? (becuase its been been 3 years). I've seen squirtle used to load maps created in **inkscape**. – ninMonkey Apr 21 '11 at 08:32
  • Squirtle is still not supporting much of SVG afaik and getting it to run with PyGame probably also requires some work. I'm searching for a long time for support of SVG drawing under Windows and 64 Bit Python and I think I have to give up. – NoDataDumpNoContribution Jan 28 '14 at 08:43
  • Time passes, new possibilities emerge. See my answer below, but wait, there's more! pynanosvg has binary package for Windows! – zgoda Jun 27 '18 at 08:50
  • pynanosvg is no longer supported. We need an updated solution. – Raj Oberoi Jan 10 '21 at 09:37
  • 1
    **SVG files are supported with Pygame Version 2.0**. Since Version 2.0.2, SDL Image supports SVG ([Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)) files (see [SDL_image 2.0](https://www.libsdl.org/projects/SDL_image)). Therefore, with pygame version 2.0.1, SVG files can be loaded into a [`pygame.Surface`](https://www.pygame.org/docs/ref/surface.html) object with [`pygame.image.load()`](http://www.pygame.org/docs/ref/image.html) – Rabbid76 Jan 06 '22 at 10:10

10 Answers10

21

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
Community
  • 1
  • 1
Johan Dahlin
  • 25,300
  • 6
  • 40
  • 55
  • 4
    any particular reason not to use `screen = pygame.display.set_mode((WIDTH, HEIGHT))` directly instead of creating this `window` object that is never used? – MestreLion Aug 15 '14 at 02:28
  • `TypeError: cannot use a str to initialize an array with typecode 'c'` – Cerin May 17 '20 at 00:21
  • 1
    You no longer need to initialise the data array explicitly. Leave out the call to array.array and instead do surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height); buf = surface.get_data(); image = pygame.image.frombuffer(buf, (width, height), "ARGB"). This will work in python3 as well as python2. Source: https://pycairo.readthedocs.io/en/latest/integration.html And yes, I know it's a very old question, but it still ranks highly in search results for pygame+cairo, so worth keeping up to date. The pygame wiki page is badly out of date at the moment. – Alexander Hanysz Oct 31 '21 at 04:04
  • 1
    Since Version 2, SDL Image supports SVG ([Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)) files (see [SDL_image 2.0](https://www.libsdl.org/projects/SDL_image)). Therefore, with pygame version 2.0.1, SVG files can be loaded into a [`pygame.Surface`](https://www.pygame.org/docs/ref/surface.html) object with [`pygame.image.load()`](http://www.pygame.org/docs/ref/image.html). – Rabbid76 Jan 06 '22 at 09:39
16

There is a new possibility that works and does not require librsvg anymore. There is the 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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
zgoda
  • 12,775
  • 4
  • 37
  • 46
  • 1
    Complete function, all-inclusive -> https://gist.github.com/zgoda/16c4bb767a085743251503471c1faeb1 – zgoda Jun 27 '18 at 10:15
  • 2
    Seemed nice, but nanosvg is no longer maintained: https://github.com/ethanhs/pynanosvg/commit/cc751ffda61c604505a405420c3fcf5689ca6807 – Kristian Benoit May 05 '20 at 15:52
  • Since Version 2, SDL Image supports SVG ([Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)) files (see [SDL_image 2.0](https://www.libsdl.org/projects/SDL_image)). Therefore, with pygame version 2.0.1, SVG files can be loaded into a [`pygame.Surface`](https://www.pygame.org/docs/ref/surface.html) object with [`pygame.image.load()`](http://www.pygame.org/docs/ref/image.html). – Rabbid76 Jan 06 '22 at 09:40
  • Nice answer! But in 2020 it seems that Pynanosvg became inactive and archived on github – nealmcb Apr 06 '23 at 18:49
16

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()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
7

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.

Torsten Marek
  • 83,780
  • 21
  • 91
  • 98
  • Since Version 2, SDL Image supports SVG ([Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)) files (see [SDL_image 2.0](https://www.libsdl.org/projects/SDL_image)). Therefore, with pygame version 2.0.1, SVG files can be loaded into a [`pygame.Surface`](https://www.pygame.org/docs/ref/surface.html) object with [`pygame.image.load()`](http://www.pygame.org/docs/ref/image.html). – Rabbid76 Jan 06 '22 at 09:40
4

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.

Alec Thomas
  • 19,639
  • 4
  • 30
  • 24
  • 1
    Since Version 2, SDL Image supports SVG ([Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)) files (see [SDL_image 2.0](https://www.libsdl.org/projects/SDL_image)). Therefore, with pygame version 2.0.1, SVG files can be loaded into a [`pygame.Surface`](https://www.pygame.org/docs/ref/surface.html) object with [`pygame.image.load()`](http://www.pygame.org/docs/ref/image.html). – Rabbid76 Jan 06 '22 at 09:40
3

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

I 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")
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pierre-Jean Coudert
  • 9,109
  • 10
  • 50
  • 59
  • SVG files are supported with Pygame Version 2.0. Since Version 2.0.2, SDL Image supports SVG ([Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)) files (see [SDL_image 2.0](https://www.libsdl.org/projects/SDL_image)). Therefore, with pygame version 2.0.1, SVG files can be loaded into a [`pygame.Surface`](https://www.pygame.org/docs/ref/surface.html) object with [`pygame.image.load()`](http://www.pygame.org/docs/ref/image.html): – Rabbid76 Jan 06 '22 at 10:08
2

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

Dickon Reed
  • 3,575
  • 4
  • 23
  • 25
  • 8
    That module has been taken down. The cairo+rsvg approaches listed below are much better solutions. [disclaimer: I quickly wrote pygamesvg 3 years ago] – PAG Aug 13 '09 at 18:57
  • 1
    Since Version 2, SDL Image supports SVG ([Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)) files (see [SDL_image 2.0](https://www.libsdl.org/projects/SDL_image)). Therefore, with pygame version 2.0.1, SVG files can be loaded into a [`pygame.Surface`](https://www.pygame.org/docs/ref/surface.html) object with [`pygame.image.load()`](http://www.pygame.org/docs/ref/image.html). – Rabbid76 Jan 06 '22 at 09:40
1

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dave
  • 11
  • 1
  • SVG files are supported with Pygame Version 2.0. Since Version 2.0.2, SDL Image supports SVG ([Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)) files (see [SDL_image 2.0](https://www.libsdl.org/projects/SDL_image)). Therefore, with pygame version 2.0.1, SVG files can be loaded into a [`pygame.Surface`](https://www.pygame.org/docs/ref/surface.html) object with [`pygame.image.load()`](http://www.pygame.org/docs/ref/image.html): – Rabbid76 Jan 06 '22 at 10:09
0

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
loopbackbee
  • 21,962
  • 10
  • 62
  • 97
  • 1
    SVG files are supported with Pygame Version 2.0. Since Version 2.0.2, SDL Image supports SVG ([Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)) files (see [SDL_image 2.0](https://www.libsdl.org/projects/SDL_image)). Therefore, with pygame version 2.0.1, SVG files can be loaded into a [`pygame.Surface`](https://www.pygame.org/docs/ref/surface.html) object with [`pygame.image.load()`](http://www.pygame.org/docs/ref/image.html): – Rabbid76 Jan 06 '22 at 10:09
0

Despite Pygame/SDL new support for SVG files, its rendering features are still very limited, so LibRsvg might still be needed. This is a 2022 update for the accepted answer that works with modern versions of Python, Pygame and Pycairo:

#!/usr/bin/env python3
import sys

import cairo
import gi
import PIL.Image
import pygame
gi.require_version('Rsvg', '2.0')
from gi.repository import Rsvg

WIDTH = 512
HEIGHT = 512
PATH = sys.argv[1] if len(sys.argv) > 1 else "test.svg"


def load_svg(path: str, size: tuple) -> pygame.Surface:
    """Render an SVG file to a new pygame surface and return that surface."""
    svg = Rsvg.Handle.new_from_file(path)

    # Create a Cairo surface.
    # Nominally ARGB, but in little-endian architectures it is effectively BGRA
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, *size)

    # Create a Cairo context and scale it
    context = cairo.Context(surface)
    context.scale(size[0]/svg.props.width, size[1]/svg.props.height)

    # Render the SVG
    svg.render_cairo(context)

    # Get image data buffer
    data = surface.get_data()
    if sys.byteorder == 'little':
        # Convert from effective BGRA to actual RGBA.
        # PIL is surprisingly faster than NumPy, but can be done without neither
        data = PIL.Image.frombuffer('RGBA', size, data.tobytes(),
                                    'raw', 'BGRA', 0, 1).tobytes()

    return pygame.image.frombuffer(data, size, "RGBA").convert_alpha()


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
image = load_svg(PATH, (WIDTH, HEIGHT))
window.blit(image, (0, 0))
pygame.display.update()

clock = pygame.time.Clock()
while True:
    if pygame.event.get([pygame.QUIT]):
        break
    clock.tick(30)
pygame.quit()

MestreLion
  • 12,698
  • 8
  • 66
  • 57