如何使用GTK3和PyGObject绘制GdkPixbuf

10

我有一个小型应用程序,使用DrawingAreaPyGObject以及GTK3绘制了一个简单的地图。

我使用以下代码加载一个Pixbuf:

from gi.repository import Gtk, GdkPixbuf
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size("logo.png", 25, 25)

然后尝试在DrawingArea的绘制事件信号中进行绘制。

def draw(self, widget, context):
    window = widget.get_window()
    ctx = window.cairo_create()
    ctx.set_source_pixbuf(pixbuf, 0, 0)

但是我收到了错误信息

"AttributeError: 'cairo.Context' object has no attribute 'set_source_pixbuf'"
如果我正确地阅读了 Gtk2 to Gtk3 迁移指南,那么这应该可以工作。我做错了什么?

你确定从get_window返回的是可绘制的内容吗? - stark
是的,我得到了一个 <gtk.gdk.X11Window object at 0xb71aab44 (GdkX11Window at 0x9db5aa0)> 对象,因此绘图区部件已实现。我似乎不知道在Gtk3中如何绘制pixbufs。 - gnirx
由于被接受的答案是“不使用pixbuf”,而真正的问题并不在于如何绘制pixbuf,您能否更改问题,以便像我这样的人来到这里寻找有关如何使用GTK3和PyGObject真正绘制GdkPixbuf的答案,而不会进入这个问题? - ilias iliadis
2个回答

10
新的draw信号使用一个回调函数,该函数已将cairo上下文作为参数传递,您不需要像在PyGtk中那样执行window = widget.get_window()等操作来获取cairo上下文,同时处理expose-event信号。在PYGObject中更加简单。
import cairo

class Foo(object):
    def __init__(self):

       (...)
        self.image = cairo.ImageSurface.create_from_png('logo.png')
       (...)

    def draw(self, widget, context):
        if self.image is not None:
            context.set_source_surface(self.image, 0.0, 0.0)
            context.paint()
        else:
            print('Invalid image')
        return False

如果您不需要PixBuf,但如果您需要它来做其他事情,您有几个选择:
  1. 将两个对象都保存在内存中。如果两者都是从PNG加载的,则除了浪费内存之外,不应该有太多问题。
  2. 将GdkPixbuf转换为PIL Image,然后将PIL Image转换为数据数组,然后使用create_for_data()从该数据数组创建Cairo ImageSurface。Yak:S我不知道更好的方法,抱歉:S
  3. 使用Gdk.cairo_set_source_pixbuf(),由hock提出。这似乎是在ImageSurface中绘制Pixbuf的正确方法,但它完全不符合Pythonic(这就是为什么我讨厌这个Introspection东西的原因,所有东西看起来像C一样,就像一个糟糕的C移植)。

如果您选择可怕的第二个选项,以下是如何操作:

import Image
import array
from gi.repository import Gtk, GdkPixbuf

width = 25
height = 25
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size('logo.png', width, height)
pil_image = Image.fromstring('RGBA', (width, height), pixbuf.get_pixels())
byte_array = array.array('B', pil_image.tostring())
cairo_surface = cairo.ImageSurface.create_for_data(byte_array, cairo.FORMAT_ARGB32, width, height, width * 4)

请注意,create_for_data()目前仅适用于Python2对于Python3尚不可用
如果您正在尝试实现双缓冲,请查看我在PyGObject中如何使用双缓冲的回答: Drawing in PyGobject (python3)
此致敬礼。

谢谢。这似乎是在GTK3中处理图形的比我的各种尝试更加明智的方式。 - gnirx
2
+1 "但这完全不符合Python的风格(这就是为令我讨厌这个内省机制的原因,所有的东西看起来都像C语言,就像一个糟糕的C语言移植版)"。 - xubuntix
更新create_for_data现已可用,当前的文档在这里https://pycairo.readthedocs.io/en/latest/reference/surfaces.html#cairo.ImageSurface.create_for_data。 - Stuart Axon

8
以下似乎可以完成任务:
def draw(self, widget, context):
    Gdk.cairo_set_source_pixbuf(context, self.pixbuf, 0, 0)
    context.paint()

一个问题仍然存在:这是否是首选的做事方式?

1
我的程序在执行“cairo_set_source_pixbuf”后生成了“分段错误”,有什么帮助吗? - Akash Shende
在 Gtk 文档中,他们说“小心,此函数不是来自 cairo ...”。我应该给这个答案上面的-1,但似乎更有建设性的是+1这个答案。Gtk3 是一种不同的方式,它使用 cairo 并返回 surface 的处理程序,因此不要将其视为外部工具:绘图发生在那里。 - cox
1
@AkashShende - 我也在使用.cairo_get_source_pixbuf时遇到了分段错误,你解决了吗? - nluigi
@nluigi 我放弃了 Pixbuf,改用 Cairo_Surface 并使用方法 cairo_set_source_surface 设置图像。请查看 https://github.com/akash0x53/i3lock/commit/f0acf46ef9738fb70cc89f9c4e43b527c3fb37d2 - Akash Shende
@AkashShende - 谢谢,对我来说问题不在于.cairo_set_source_pixbuf,而是我所做的GdkPixbuf.Pixbuf.new_from_data调用。更改GdkPixbuf.Pixbuf.new_from_bytes解决了我的问题。 - nluigi

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