3

我正在开发一个用 Cairo + Gtk 编写的应用程序。请注意,由于追溯兼容性问题,我被迫使用 Python 作为编程语言,PyGTK 作为包装器,以及 GTK 库 v.2.24。没有机会使用 C/C++ 和/或 GTK3!

我的应用程序需要(重新)在屏幕上为每次调用暴露方法(显然)绘制大量数据

我只是让用户有机会手动选择以前用 Cairo 绘制的一些对象。由于我在 gtk.DrawingAreas 上绘图,看来我必须手动实现橡皮筋选择功能。

这是问题:

有没有办法在每次鼠标移动时重绘橡皮筋矩形,避免重绘屏幕上的所有其他对象?

出于性能原因,我只会重绘选择矩形

由于大量的图形对象,我的 GUI 非常慢。不幸的是,尽管多次尝试,我别无选择:重绘全部或重绘任何东西!

我想到的第一件事是:有没有办法在具有大部分数据的绘图区和鼠标光标之间覆盖一个中间级别?通过调用 queue_draw_area() 函数没有性能提升。

下面是一个简单的、自包含的代码示例:显然,在这种情况下,我只使用 cairo 来绘制极其简单的图形对象。

import gtk
from gtk import gdk

class Canvas(gtk.DrawingArea):

    # the list of points is passed as argument

    def __init__(self, points):
        super(Canvas, self).__init__()
        self.points = points
        self.set_size_request(400, 400)

        # Coordinates of the left-top angle of the selection rect
        self.startPoint = None

        self.endPoint = None

        # Pixmap to drawing rubber band selection
        self.pixmap = None

        self.connect("expose_event", self.expose_handler)
        self.connect("motion_notify_event", self.mouseMove_handler)
        self.set_events(gtk.gdk.EXPOSURE_MASK
                            | gtk.gdk.LEAVE_NOTIFY_MASK
                            | gtk.gdk.BUTTON_PRESS_MASK
                            | gtk.gdk.POINTER_MOTION_MASK
                            | gtk.gdk.POINTER_MOTION_HINT_MASK)

    # Method to paint lines and/or rubberband on screen

    def expose_handler(self, widget, event):

        rect = widget.get_allocation()
        w = rect.width
        h = rect.height
        ctx = widget.window.cairo_create()
        ctx.set_line_width(7)
        ctx.set_source_rgb(255, 0, 0)
        ctx.save()

        for i in range(0, len(self.points)):
            currPoint = self.points[i]
            currX = float(currPoint[0])
            currY = float(currPoint[1])
            nextIndex = i + 1
            if (nextIndex == len(self.points)):
                continue
            nextPoint = self.points[nextIndex]
            nextX = float(nextPoint[0])
            nextY = float(nextPoint[1])
            ctx.move_to(currX, currY)
            ctx.line_to(nextX, nextY)
        ctx.restore()
        ctx.close_path()
        ctx.stroke()

        # rubber band
        if self.pixmap != None:
            width = self.endPoint[0] - self.startPoint[0]
            height = self.endPoint[1] - self.startPoint[1]
            if width < 0 or height < 0:
                tempEndPoint = self.endPoint
                self.endPoint = self.startPoint
                self.startPoint = tempEndPoint

            height = self.endPoint[1] - self.startPoint[1]
            width = self.endPoint[0] - self.startPoint[0]
            widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL], self.pixmap, self.startPoint[0], self.startPoint[1], self.startPoint[0], self.startPoint[1], abs(width), abs(height))

    def mouseMove_handler(self, widget, event):
        x, y, state = event.window.get_pointer()
        if (state & gtk.gdk.BUTTON1_MASK):
            if (state & gtk.gdk.CONTROL_MASK):
                if self.startPoint == None:
                    self.startPoint = (x,y)
                    self.endPoint = (x,y)
                else:
                    self.endPoint = (x,y)
                tempPixmap = gtk.gdk.Pixmap(widget.window, 400, 400)

                height = self.endPoint[1] - self.startPoint[1]
                width = self.endPoint[0] - self.startPoint[0]

                gc = self.window.new_gc()
                gc.set_foreground(self.get_colormap().alloc_color("#FF8000"))

                gc.fill = gtk.gdk.STIPPLED
                tempPixmap.draw_rectangle(gc, True, self.startPoint[0], self.startPoint[1], abs(width), abs(height))

                self.pixmap = tempPixmap
                # widget.queue_draw_area()
                widget.queue_draw()
        else:
            if (self.pixmap != None):
                self.pixmap = None

                # ...do something...

                # widget.queue_draw_area(self.startPoint[0], self.startPoint[1], )
                widget.queue_draw()


li1 = [(20,20), (380,20), (380,380), (20,380)]

window = gtk.Window()
canvas = Canvas(li1)
window.add(canvas)
window.connect("destroy", lambda w: gtk.main_quit())
window.show_all()
gtk.main()

谢谢!

4

2 回答 2

2

使用 Gtk+/Cairo 绘图时的一些一般提示:

  • 尽可能多地绘制。这个想法是跟踪已更改的区域,并仅重绘那些区域。Gdk 会自动为您完成这部分工作。当它调用expose(在 Gtk3 中draw)时,它会应用一个剪贴蒙版,因此只有“无效”像素会被您的绘图更改。
  • 你可以告诉 Gdk 哪些区域应该被重绘GdkWindow.invalidate_rect。现在您的调用widget.queue_draw使整个窗口无效,因此您绘制了太多像素。
  • 如果您在非无效区域中有复杂的元素,您仍然会在暴露中绘制/计算它们 - 它们只是不会出现在屏幕上。为此,您可以检查event.area(a GdkRectangle)。如果您的元素不与该区域相交,则不必费心绘制它们,因为这些像素无论如何都会被剪裁。
    • 这里有一个权衡。有时,如果您计算一个元素是否可见,它会节省很多。如果节省了大量的几何计算,有时只绘制几个额外的像素会更快。你必须逐案决定。
  • 可能会为每个 invalidate_rect 调用一次 Expose,但 Gdk 也可能会在您使几个小/重叠的矩形无效时识别出来,并且只用较大的矩形调用一次。
  • 你不应该混合“原始”Gdk 和 cairo 调用。Gdk 调用(在 gc 上操作)在 Gtk3 中消失了。现在您说您只在 Gtk2 中编写此代码,但如果您希望有一天在另一个项目中重用该代码,那么您现在使用 Cairo 编写它会有所帮助。也可能存在一些性能差异。
  • 您应该注意抗锯齿。默认情况下,cairo 中的所有内容都是抗锯齿的(我不知道您是否可以将其关闭)。大多数时候这是一件好事,但有时清晰的像素看起来更好 - 而且它们也快得多。如果您想要非抗锯齿矩形,请在整数坐标上绘制填充矩形,在半整数坐标上绘制描边矩形(1px 线)。
  • 在您的具体示例中,您的 Pixmap 似乎没有必要。您可以尝试的事情是在像素图或 cairo 表面发生变化时将其渲染,然后expose从像素图中复制无效区域,并在顶部绘制橡皮筋。但是,我发现直接进行绘图通常更容易和更快。如果您正在执行此手动缓冲,您可能需要考虑禁用内置双缓冲 ( GtkWidget.set_double_buffered) 和自动绘制背景 ( GtkWidget.set_app_paintable)。

这是我制作的一个小程序:rubberband.py。我从我的一个项目中获取了代码,并添加了几个可以选择的圆圈。希望您可以以此为起点。

于 2013-04-18T16:49:27.777 回答
1

我知道旧线程,但一个模型可能会将渲染表面复制到另一个缓冲区,然后重绘无效区域。可以在此处找到 Julia 语言的该技术示例。在实践中,这似乎取得了非常好的性能。

于 2014-09-19T09:15:46.030 回答