Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

SVG rendering in a PyGame application. Prior to Pygame 2.0, Pygame did not support SVG. Then how did you load it?

Ask Question

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)

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, 2011 at 8: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. Trilarion Jan 28, 2014 at 8:43 Time passes, new possibilities emerge. See my answer below, but wait, there's more! pynanosvg has binary package for Windows! zgoda Jun 27, 2018 at 8:50 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() Rabbid76 Jan 6, 2022 at 10:10

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
                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, 2014 at 2:28
                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: 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, 2021 at 4:04
                Since Version 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().
– Rabbid76
                Jan 6, 2022 at 9:39

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.

Complete function, all-inclusive -> gist.github.com/zgoda/16c4bb767a085743251503471c1faeb1 – zgoda Jun 27, 2018 at 10:15 Since Version 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(). – Rabbid76 Jan 6, 2022 at 9:40

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()
                Since Version 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().
– Rabbid76
                Jan 6, 2022 at 9:40
                Since Version 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().
– Rabbid76
                Jan 6, 2022 at 9:40
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")
                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():
– Rabbid76
                Jan 6, 2022 at 10:08
                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, 2009 at 18:57
                Since Version 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().
– Rabbid76
                Jan 6, 2022 at 9:40

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
                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():
– Rabbid76
                Jan 6, 2022 at 10:09

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
                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():
– Rabbid76
                Jan 6, 2022 at 10:09

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

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.