performance - Cairo + rubber band selection: gui very very slow -
i'm working on application written in cairo + gtk. please note that, due retrocompatibility issues, forced use python programming language, pygtk wrapper, , gtk libraries, v.2.24. no chance use c/c++ and/or gtk3!
my app need (re)draw big amount of data on screen each invocation of expose method(obviously).
i give users chance manually select objects drawn cairo. since i'm drawing on gtk.drawingareas, seems must manually implement rubber band selection functionality.
this question:
is there way redraw rubber band rectangle @ every mouse move, avoiding redraw others objects on screen?
i redraw only selection rectangle performance reasons.
due big amount of graphical objects, gui terribly slow. unfortunately, despite several attempts, have no choice: redraw or redraw anything!
first thing came in mind: there way overlay intermediate level between drawingarea of data , mouse cursor? calling queue_draw_area() function there aren't performance gains.
a simple, self containing code example below: obviously, in case ionly use cairo draw extremely simple graphic objects.
import gtk gtk import gdk class canvas(gtk.drawingarea): # list of points passed argument def __init__(self, points): super(canvas, self).__init__() self.points = points self.set_size_request(400, 400) # coordinates of left-top angle of selection rect self.startpoint = none self.endpoint = none # pixmap 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 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() in range(0, len(self.points)): currpoint = self.points[i] currx = float(currpoint[0]) curry = float(currpoint[1]) nextindex = + 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
some general tips when drawing gtk+/cairo:
- draw as neccessary. idea keep track of areas have changed, , redraw those. gdk in parts automatically you. when calls
expose
(in gtk3draw
), applies clipping mask "invalidated" pixels changed drawings. - you can tell gdk areas should redrawn
gdkwindow.invalidate_rect
. right callwidget.queue_draw
invalidates whole window, draw many pixels. - if have complex elements in non-invalidated area, you'd still draw / calculate them in expose - they'd not make screen. this, can check
event.area
(a gdkrectangle). if elements dont intersect area, don't have bother drawing them, pixels clipped anyway.- here lies trade-off. saves lot if calculate whether element visible or not. it's faster draw few pixels if saves lot of geometry calculations. have decide case case.
- expose may called once each invalidate_rect, it's possible gdk recognizes when invalidate several small/overlapping rects, , calls once larger rect.
- you shouldn't mix 'raw' gdk , cairo calls. gdk calls (operating on gc) going away in gtk3. writing in gtk2, if want reuse code 1 day in project, it'll if write cairo. there might performance differences, too.
- you should aware of anti-aliasing. default, antialiased in cairo (and don't know whether can turn off). that's thing of time, crisp pixels nicer - , lot faster, too. if want non-antialiased rectangles, draw filled rectangles on integer coordinates, , stroked rectangles (1px lines) on half-integer coordinates.
- in concrete example, pixmap seems unneccessary. on thing try though render stuff pixmap or cairo surface when changes, in
expose
copy invalidated areas pixmap over, , draw rubber band on top. however, find it's easier , faster drawing directly. if doing manual buffering, might want think disabling built-in double-buffering (gtkwidget.set_double_buffered
) , automatic drawing of background (gtkwidget.set_app_paintable
).
here little program make: rubberband.py. took code project of mine , added couple of circles 1 can select. hope can use starting point.
Comments
Post a Comment