Reputation: 321
I'm working on an application written in Cairo + Gtk. Please note that, due to retrocompatibility issues, I am forced to use Python as programming language, PyGTK as wrapper, and GTK libraries, v.2.24. No chance to use C/C++ and/or GTK3!
My app need to (re)draw a big amount of data on screen for each invocation of expose method(obviously).
I would simply give to users a chance to manually select some objects previously drawn with Cairo. Since I'm drawing on gtk.DrawingAreas, it seems that I must manually implement the rubber band selection functionality.
This is the question:
Is there any way to redraw the rubber band rectangle at every mouse move, avoiding to redraw all others objects on screen?
I would redraw only the selection rectangle for performance reasons.
Due to the big amount of graphical objects, my GUI is terribly slow. Unfortunately, despite several attempts, I have no choice: redraw all or redraw anything!
First thing that came in my mind: is there any way to overlay an intermediate level between the DrawingArea with most of data and the mouse cursor? by calling queue_draw_area() function there aren't performance gains.
A simple, self containing code example below: obviously, in this case Ionly use cairo to draw extremely simple graphic objects.
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()
Thanks!
IT
Upvotes: 3
Views: 2003
Reputation: 12179
Old thread I know, but one model might be copy the rendered surface to another buffer, and then redraw the invalidated area. An example of that technique for the Julia language can be found here. In practice that seems to achieve very good performance.
Upvotes: 1
Reputation: 10030
Some general tips when drawing with Gtk+/Cairo:
expose
(in Gtk3 draw
), it applies a clipping mask so only the "invalidated" pixels are changed by your drawings.GdkWindow.invalidate_rect
. Right now your call to widget.queue_draw
invalidates the whole window, so you draw too many pixels.event.area
(a GdkRectangle). If your elements dont intersect this area, you don't have to bother drawing them, as those pixels are clipped anyway.
expose
copy the invalidated areas from the pixmap over, and draw the rubber band on top. However, I find that it's often easier and faster to just do the drawing directly. If you are doing this manual buffering, you might want to think about disabling the built-in double-buffering (GtkWidget.set_double_buffered
) and the automatic drawing of the background (GtkWidget.set_app_paintable
).Here is a little program I make: rubberband.py. I took the code from a project of mine and added a couple of circles one can select. Hope you can use it as a starting point.
Upvotes: 2