Pygame 应用程序中的 SVG 渲染。 在 Pygame 2.0 之前,Pygame 不支持 SVG。 那你是怎么加载的呢?

发布于 2024-07-06 02:55:38 字数 255 浏览 10 评论 0原文

Pygame 应用程序中,我想渲染 SVG 中描述的无分辨率 GUI 小部件。

我怎样才能实现这个目标?

(我喜欢 OCEMP GUI 工具包,但它的渲染似乎依赖于位图。 )

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

画中仙 2024-07-13 02:55:38

这是一个完整的例子,结合了其他人的提示。
它应该从当前目录渲染一个名为 test.svg 的文件。 它在 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

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
余生再见 2024-07-13 02:55:38

Pygame 2.0 版支持 SVG 文件。 自版本 2.0.2 起,SDL Image 支持 SVG (可缩放矢量图形) 文件(请参阅SDL_image 2.0)。 因此,在 pygame 版本 2.0.1 中,SVG 文件可以加载到 pygame 中。使用pygame.image.load() 表面对象

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

在 Pygame 2 之前,您必须实现可缩放矢量图形加载与其他图书馆。 以下是有关如何执行此操作的一些想法。


一个非常简单的解决方案是使用 CairoSVG。 使用函数 cairosvg.svg2png矢量图形 (SVG) 文件可以直接转换为[便携式网络图形(PNG)]文件

安装CairoSVG

pip install CairoSVG

编写一个将 SVF 文件转换为 PNG 的函数 (ByteIO< /a>)并创建一个 pygame.Surface对象可能如下所示:

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)

另请参阅 加载 SVG< /a>


另一种方法是使用svglib。 但是,透明背景似乎存在问题。 关于此主题有一个问题 如何使 png 背景透明? #171.

安装svglib

pip install svglib

解析和光栅化 SVG 文件并创建 pygame.Surface 对象可能如下所示:

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)

另一个简单的解决方案是使用pynanosvg。 该解决方案的缺点是 Nanosvg 不再受到积极支持,并且无法与 Python 3.9 一起使用。 pynanosvg 可用于加载和光栅化矢量图形 (SVG) 文件。 安装 Cythonpynanosvg

pip install Cython
pip install pynanosvg

SVG 文件可以被读取、光栅化并加载到 pygame.Surface 对象具有以下功能:

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)

最小示例:

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

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()
不忘初心 2024-07-13 02:55:38

有一种新的可能性可以工作,并且不再需要 librsvg 。 有 nanosvg 库上的 Cython 包装器 并且它有效:

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)

我发现 Cairo/rsvg 解决方案太复杂而无法获得由于依赖项的安装非常困难,所以无法正常工作。

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.

陌上芳菲 2024-07-13 02:55:38

您可以使用 Cairo (与 PyCairo 一起),它支持渲染 SVG。 PyGame 网页有一个 HOWTO,用于使用 Cairo 渲染到缓冲区,并直接使用该缓冲区PyGame。

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.

葬花如无物 2024-07-13 02:55:38

我意识到这并不能完全回答你的问题,但是有一个名为 Squirtle 的库可以使用 Pyglet 或 PyOpenGL 渲染 SVG 文件。

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.

夏雨凉 2024-07-13 02:55:38

Cairo 无法直接呈现 SVG。
看来我们必须使用librsvg。

我刚刚找到了这两页:

像这样的东西可能应该有效(渲染 test.svgtest.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")

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")
久夏青 2024-07-13 02:55:38

pygamesvg 似乎可以做你想做的事情(尽管我还没有尝试过)。

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

不知在何时 2024-07-13 02:55:38

当我运行它时,最后一条评论崩溃了,因为 svg.render_cairo() 期望的是 Cairo 上下文而不是开罗表面。 我创建并测试了以下函数,它似乎在我的系统上运行良好。

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

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
爺獨霸怡葒院 2024-07-13 02:55:38

尽管 Pygame/SDL 对 SVG 文件的新支持,其渲染功能仍然非常有限,因此可能仍然需要 LibRsvg。 这是已接受答案的 2022 年更新,适用于现代版本的 Python、Pygame 和 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()

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

空心空情空意 2024-07-13 02:55:38

基于其他答案,这里有一个将 SVG 文件读入 Pygame 图像的函数,包括纠正颜色通道顺序和缩放:

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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文