Cairo 是一个广泛使用的 2D 图形库,支持多种输出设备,包括屏幕、PDF、SVG等。Cairo 的核心是它的“context”(上下文)概念,这是进行所有绘制操作的中心点。在 Cairo 中,context 通常与特定的图形目标(或称作“surface”)关联,如窗口或图像文件。
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 非常适合于需要高度图形处理和跨平台图形输出的应用程序。