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
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)
–
–
–
–
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.
–
–
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()
–
–
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")
–
–
–
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
–
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.