Cairo 是一个广泛使用的 2D 图形库,支持多种输出设备,包括屏幕、PDF、SVG等。Cairo 的核心是它的“context”(上下文)概念,这是进行所有绘制操作的中心点。在 Cairo 中,context 通常与特定的图形目标(或称作“surface”)关联,如窗口或图像文件。

Cairo context 和持久性_键盘输入

1、问题背景

在使用 pycairo 绘制时,用户遇到了一个有趣的错误。该程序创建了一个简单的 GTK+ 窗口,在上面绘制一个矩形,然后有一个回调函数在键盘输入时绘制一条随机线条。但是,每次键盘输入时,用户都需要创建一个新的上下文,否则在程序收到第一个键盘输入时就会出现错误(特别是在 .stroke() 行上)。错误如下,如有必要。'BadDrawable (invalid Pixmap or Window parameter)'。

以下是代码的详细信息:

#! /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)

2、解决方案一:

不使用双缓冲

Cairo 图形无法持久保存。因此,您需要在曝光处理程序中完成所有绘制,否则,正如您已经发现的那样,它将在每次窗口重绘时消失。 Cairo 上下文不会持久存在,因为使用了双缓冲:请参阅 C 文档中的注释,不幸的是,我在 PyGTK 文档中找不到任何地方。 在上面的代码中,您应该在按键处理程序中生成随机线的坐标和颜色,并将它们保存在一个数组中。然后在曝光处理程序中,按顺序绘制数组中的每条线。 代码实现如下:

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" }

  def __init__(self):
      super(Screen, self).__init__()
      # Create an array to store the coordinates and color of the random lines
      self.lines = []

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

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

    # Draw the random lines
    for line in self.lines:
      self.cr.set_source_rgb(*line['color'])
      self.cr.move_to(*line['start'])
      self.cr.line_to(*line['end'])
      self.cr.stroke()

  def key_press_event(self, *args):
    # Generate the coordinates and color of the random line
    start = [z/2.0 for z in self.window.get_size()]
    end = [z*random.random() for z in self.window.get_size()]
    color = [random.random(), random.random(), random.random()]

    # Add the line to the array
    self.lines.append({'start': start, 'end': end, 'color': color})

    # Queue a redraw of the widget
    self.queue_draw()

# Create a GTK+ window, attach to the exit button, and whatever is passed as arg becomes the body of the window.
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)

解决方案二:

禁用双缓冲

虽然每次运行都必须创建上下文,但您可以通过禁用小部件的双缓冲来实现您正在寻找的持久性。

在代码中,您可以调用 set_double_buffered(False) 方法来禁用双缓冲。 以下是使用 Hamster Graphics 库的一个示例,它可以满足您的要求:

import hamster
from hamster import graphics

# Create a window
window = graphics.window(640, 480, "Many Lines")

# Disable double buffering
window.set_double_buffered(False)

# Create an array to store the lines
lines = []

# Draw a line when a key is pressed
def on_key_press(event):
  # Generate the coordinates and color of the random line
  start = [random.randint(0, window.width), random.randint(0, window.height)]
  end = [random.randint(0, window.width), random.randint(0, window.height)]
  color = [random.random(), random.random(), random.random()]

  # Add the line to the array
  lines.append({'start': start, 'end': end, 'color': color})

  # Draw the line
  graphics.set_color(*color)
  graphics.move_to(*start)
  graphics.line_to(*end)
  graphics.stroke()

# Handle key press events
window.bind("<KeyPress>", on_key_press)

# Start the event loop
window.mainloop()

在这两个例子中,Cairo context 的持久性是通过将绘图结果保存到文件中实现的。这使得 Cairo 非常适合于需要高度图形处理和跨平台图形输出的应用程序。