我该如何渲染 SVG 文件的 *部分*?

7
我想按名称渲染svg文件的部分,但我无论如何都无法弄清楚如何做到这一点(使用python + gtk)。
以下是相关的svg文件:http://david.bellot.free.fr/svg-cards/files/SVG-cards-2.0.1.tar.gz(更新:该文件不再存在,但您可以在http://svg-cards.sourceforge.net/上追踪它)
在他的网站上,David说:

您可以通过以下两种方式之一绘制卡片: 将文件呈现为像素图并手动剪切每张卡片,或者 通过DOM接口使用卡片名称。 所有卡片都嵌入到SVG组中。

我不知道他所说的DOM接口是什么意思。我进行了一些搜索,并找到了最符合我想做的事情的最佳结果:
QSvgRenderer *renderer = new QSvgRenderer(QLatin1String("SvgCardDeck.svg"));
QGraphicsSvgItem *black = new QGraphicsSvgItem();
QGraphicsSvgItem *red   = new QGraphicsSvgItem();

black->setSharedRenderer(renderer);
black->setElementId(QLatin1String("black_joker"));

red->setSharedRenderer(renderer);
red->setElementId(QLatin1String("red_joker"));

请注意,这是关于Qt的内容,甚至不是用Python编写的。
目前为止,我的翻译如下:
#!/usr/bin/env python

from __future__ import absolute_import

import cairo
import gtk
import rsvg

from xml import xpath
from xml.dom import minidom

window = gtk.Window()
window.set_title("Foo")
window.set_size_request(256, 256)
window.set_property("resizable", False)
window.set_position(gtk.WIN_POS_CENTER)
window.connect("destroy", gtk.main_quit)
window.show()

document = minidom.parse("cards.svg")
element = xpath.Evaluate("//*[@id='1_club']", document)[0]
xml = element.toxml()

svg = rsvg.Handle()
svg.write(xml)

pixbuf = svg.get_pixbuf()

image = gtk.Image()
image.set_from_pixbuf(pixbuf)
image.show()

window.add(image)

gtk.main()

当然它不起作用。

我错过了什么?

5个回答

9

用于渲染SVG的GTK库称为RSVG。它有Python绑定,但是它们没有文档,并且它们不包装rsvg_handle_get_pixbuf_sub()rsvg_handle_render_cairo_sub()函数,这些函数在C中通常用于此目的。据我所知,您需要执行以下操作。像Adam Crossland建议的那样提取XML节点。要呈现它,您需要执行以下操作:

import gtk
import rsvg
handle = rsvg.Handle()
handle.write(buffer=xml_data) 
# xml_data is the XML string for the object you want
image = gtk.Image()
image.set_from_pixbuf(handle.get_pixbuf())

如果您想在gtk.Image中使用它,那么可以这样做,否则可以使用pixbuf进行其他操作。您还可以使用handle.render_cairo(cr)将其呈现到Cairo上下文中,其中cr是您的Cairo上下文。
编辑:抱歉,我一开始没有仔细阅读Python绑定。 _sub()函数是使用id=参数实现的,因此您的程序可以简化为以下内容:
#!/usr/bin/env python

import gtk
import rsvg

window = gtk.Window()
window.set_title("Foo")
window.connect("destroy", gtk.main_quit)
window.show()

svg = rsvg.Handle(file='cards.svg')
pixbuf = svg.get_pixbuf(id='#3_diamond')

image = gtk.Image()
image.set_from_pixbuf(pixbuf)
image.show()

window.add(image)

gtk.main()

我测试过这个方法并且它是有效的。然而,窗口的大小是整个SVG画布的大小,并被剪裁到屏幕大小(这就是为什么我渲染了方块3而不是在角落里的梅花A)。因此,你仍然需要找到一些方法来裁剪你想要的卡片周围的像素缓冲区,但这应该不太难。

我也尝试过这种方法,但无法弄清楚它为什么会这样做(因此我尝试其他解决方案,看看是否会得到类似的结果)。尽管窗口大小是整个SVG画布的大小,但这个解决方案回答了我的问题(当然,也引发了另一个问题)。 :) - Fake Code Monkey Rashid
这非常有帮助,尽管更多的帮助也会很有用。要呈现SVG图像的一部分,您可以使用handle.render_cairo(cr=c,id ="#layer_5"),其中"c"是cairo上下文。对我来说完全不明显的部分是,id开头的"#"是必需的。我的SVG具有类似'id="layer_1"'的id,我可以使用render_cairo(cr=c,id ="#layer_1")呈现单个图层。 - bodgesoc

2
这是我对裁剪空白区域问题的解答。这只是一个粗略的技巧,但它非常有效。对于任何使用Python制作卡牌游戏的人来说,这也是一个很好的起点。
import gtk
import rsvg
svg = rsvg.Handle(file="/usr/share/gnome-games-common/cards/gnomangelo_bitmap.svg")
w, h = 202.5, 315
card_names = map(str, range(1,11)) + ["jack", "queen", "king"]
suites = ["club", "diamond", "heart", "spade"]
specials = [{"name":"black_joker","x":0, "y":4}, {"name":"red_joker","x":1, "y":4}, {"name":"back","x":2, "y":4}]
for suite_number, suite in enumerate(suites):
    for card_number, card in enumerate(card_names):
        print "processing", suite, card, '#'+card+'_'+suite
        pixbuf = svg.get_pixbuf(id='#'+card+'_'+suite)
        pixbuf.subpixbuf(int(w*card_number), int(h*suite_number), int(w), int(h)).save("./"+card+"_"+suite+".png","png", {})
for special in specials:
    print "processing", special["name"]
    pixbuf = svg.get_pixbuf(id='#'+special["name"])
    card_number = special["x"]
    suite_number = special["y"]
    pixbuf.subpixbuf(int(w*card_number), int(h*suite_number), int(w), int(h)).save("./"+special["name"]+".png","png", {})

2

这里稍微挖掘一下过去,但是ptomato在2010年给出的答案,现在在Gtk3中也能用,只需要做一些微小的修改。以下代码将仅呈现3个方块(svg ID)。

#!/usr/bin/env python

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Rsvg', '2.0')

from gi.repository import Gtk, Rsvg

svg = Rsvg.Handle.new_from_file('svg-cards.svg')

pixbuf = svg.get_pixbuf_sub('#3_diamond')

image = Gtk.Image()
image.set_from_pixbuf(pixbuf)
image.show()

window = Gtk.Window()
window.set_title("Foo")
window.connect("destroy", Gtk.main_quit)
window.show()
window.add(image)

Gtk.main()

1

我认为他所说的“通过DOM接口”是指,由于SVG是XML格式,因此您可以在minidom或其他Python XML解析器中加载SVG文件,并提取具有特定名称的XML节点。该XML节点应该代表一个可呈现的项目。


你如何渲染XML节点? - Fake Code Monkey Rashid

-1

这并不能帮助我通过名称检索SVG文件的部分,然后将它们呈现出来。 - Fake Code Monkey Rashid
你可以尝试获取SVG文件中命名部分的边界框,并相应地设置viewBox。 - joeforker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接