Cairo上下文与持久化?

3 投票
3 回答
3180 浏览
提问于 2025-04-16 02:58

我刚开始使用pycairo,遇到了一个有趣的错误。我写的程序创建了一个简单的gtk窗口,在上面画了一个矩形,然后设置了一个回调函数,可以在任何键盘输入时画一条随机的线。不过,似乎每次键盘输入时,我都必须创建一个新的上下文,否则在程序第一次接收到键盘输入时(具体是在调用.stroke()这一行时)就会出错。错误信息是这样的,如果有用的话:'BadDrawable (无效的Pixmap或窗口参数)'。 (详细信息:序列号230 错误代码9 请求代码53 次要代码0)

#! /usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo, math, random
# Create a GTK+ widget on which we will draw using Cairo
class Screen(gtk.DrawingArea):
# Draw in response to an expose-event
  __gsignals__ = { "expose-event": "override" }

  # Handle the expose-event by drawing
  def do_expose_event(self, event):
    # Create the cairo context
    self.cr = self.window.cairo_create()
    # Restrict Cairo to the exposed area; avoid extra work
    self.cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
    self.cr.clip()

    self.draw(*self.window.get_size())

  def key_press_event(self, *args):
    # print args
    self.cr = self.window.cairo_create() # This is the line I have to add
    # in order to make this function not throw the error. Note that cr is only
    # given as attribute of self in order to stop it going out of scope when this line
    # doesn't exist
    self.cr.set_source_rgb(random.random(), random.random(), random.random())
    self.cr.move_to(*[z/2.0 for z in self.window.get_size()])
    self.cr.line_to(*[z*random.random() for z in self.window.get_size()])
    self.cr.stroke()

  def draw(self, width, height):
    # Fill the background with gray
    self.cr.set_source_rgb(.5,.5,.5)
    self.cr.rectangle(0, 0, width,height)
    self.cr.fill()

    self.cr.set_source_rgb(1,0,0)
    self.cr.arc(width/2.0, height/2.0, min(width,height)/2.0 - 20.0, 0.0, 2.0*math.pi)
    self.cr.stroke()

#create a gtk window, attach to exit button, and whatever is passed as arg becomes the body of the window. AWESOME
def run(Widget):
  window = gtk.Window()
  widget = Widget()
  window.connect("delete-event", gtk.main_quit)
  window.connect('key-press-event',widget.key_press_event)
  widget.show()
  window.add(widget)
  window.present()
  gtk.main()

if __name__ == "__main__":
  run(Screen)

谢谢你的帮助!

(更新:我在玩的时候发现,当我调整窗口大小时,所有新添加的对象都会被删除(或者至少不再出现?))

3 个回答

0

有很多种持久化的方式可以讨论:

某些表面上画的东西是不会持久的,比如图形用户界面(GUI)上的表面。你需要在暴露回调中重新绘制它们。

PyCairo对象不应该被当作持久化对象来看待,而只是作为C语言中Cairo库函数的接口。

Cairo上下文中的内容(路径和填充)在执行stroke()或fill()操作后不会保持。

GUI表面的上下文在暴露事件之间不会保持(可能是因为双缓冲?)。我不确定其他表面(比如设备)上的上下文是否会保持。所以你不能用cairo上下文来存储视口的属性(视口就是用户坐标系下文档的窗口)。

视觉持久性是指人眼在光线消失后仍能看到光的倾向。在动画或视频中,鬼影和闪烁就是这种现象的表现。禁用双缓冲可以让你看到绘制的内容,也就是说,在一个暴露事件中实现动画(模拟视觉持久性的症状)。但是,禁用双缓冲并不会让GUI表面的上下文在暴露事件之间保持。

记忆的持久性才是真正的持久性,或者我应该说是超现实的。

1

虽然每次运行时都需要创建上下文,但你可以通过关闭小部件的双缓冲来实现你想要的持久性。

下面是一个使用hamster图形库的例子,它就是这样做的:

https://github.com/projecthamster/experiments/blob/master/many_lines.py

2

Cairo绘图不会保存下来。最好不要把它们想成“对象”——这跟画布库不一样,你不能在绘制后随意移动或变换它们。你必须在暴露处理函数中完成所有的绘图工作,否则就像你发现的那样,窗口每次重绘时,它们都会消失。

Cairo上下文不保存的原因是因为使用了双缓冲技术:可以参考C语言文档中的说明,不过我在PyGTK的文档中找不到相关内容。

在上面的代码中,你应该在按键处理函数中生成随机线条的坐标和颜色,并把它们保存在一个数组里。然后在暴露处理函数中,按照顺序绘制数组中的每一条线。

撰写回答