2013-04-18 113 views
3

我正在開發一個用開羅+ Gtk編寫的應用程序。 請注意,由於retrocompatibility問題,我被迫使用Python作爲編程語言,PyGTK作爲包裝,以及GTK庫,v.2.24。 沒有機會使用C/C++和/或GTK3!開羅+橡皮筋選擇:gui非常非常慢

我的應用程序需要(重新)繪製的數據在屏幕上量暴露方法(顯然)的每次調用。

我只是給用戶一個機會手動選擇以前用開羅繪製的一些對象。 因爲我在繪製gtk.DrawingAreas,所以我似乎必須手動實現橡皮筋選擇功能。

這是一個問題:

有什麼辦法重繪每鼠標移動橡皮筋矩形,避免重繪所有其他屏幕上的對象?

我會重繪只有出於性能原因的選擇矩形。

由於大量的圖形對象,我的GUI非常慢。不幸的是,儘管多次嘗試,我別無選擇:重繪全部或重繪任何東西!

第一件事就在我腦海裏:有沒有什麼辦法在DrawingArea與大部分數據和鼠標光標之間覆蓋中間級別?通過調用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() 

謝謝!

IT

用GTK + /開羅繪圖時

回答

2

的一般提示:

  • 只畫出多達neccessary。這個想法是跟蹤已經改變的區域,並重新繪製這些區域。 Gdk會自動爲你自動執行此操作。當它調用expose(在Gtk3 draw中)時,它會應用一個剪貼蒙版,這樣只有「無效」像素纔會被您的繪圖所改變。
  • 你可以告訴Gdk哪些區域應該用GdkWindow.invalidate_rect重繪。現在您撥打widget.queue_draw會導致整個窗口無效,因此您繪製的像素太多。
  • 如果你有複雜的元素在非失效區域,你仍然可以在揭露中繪製/計算它們 - 他們只是不會將它放到屏幕上。爲此,您可以檢查event.area(一個GdkRectangle)。如果你的元素不與這個區域相交,你不必費心繪製它們,因爲無論如何這些像素都被剪切掉了。
    • 這裏是一個權衡。有時候,如果你計算一個元素是否可見,它可以節省很多。如果節省了大量的幾何計算,有時只畫幾個額外的像素會更快。你必須決定個案。
  • 對於每個invalidate_rect,Expose可能會被調用一次,但是也可能Gdk會識別出何時使多個小/重疊的rects失效,並且只用一個更大的rect調用它。
  • 你不應該混合'原始'Gdk和開羅電話。 Gdk電話(在gc上運行)正在Gtk3中消失。現在,您只是在Gtk2中說出了您的這種寫法,但如果您希望有一天在另一個項目中重新使用代碼,那麼現在您可以使用Cairo編寫代碼。也可能有一些性能差異。
  • 您應該知道抗鋸齒。默認情況下,cairo中的所有內容都是antialiased(我不知道是否可以關閉它)。這在大多數時候都是很好的,但有時候清晰的像素看起來更好 - 而且速度也更快。如果您想要非反鋸齒矩形,請在整數座標上繪製填充的矩形,並在半整數座標上繪製矩形(1px線)。
  • 在您的具體示例中,您的Pixmap似乎不是必需的。在你可以嘗試的東西是當你的東西變成一個像素圖或開羅表面時,然後在expose從pixmap複製失效區域,並在頂部繪製橡皮筋。但是,我發現直接進行繪圖通常更簡單快捷。如果您正在執行此手動緩衝,則可能需要考慮禁用內置雙緩衝(GtkWidget.set_double_buffered)和自動繪製背景(GtkWidget.set_app_paintable)。

這是我製作的一個小程序:rubberband.py。我從我的一個項目中獲取了代碼,並添加了幾個可供選擇的圓圈。希望你可以用它作爲出發點。

+0

您好!請檢查我的[關於在GTK中繪圖的問題](http://stackoverflow.com/questions/26497939/gtk-theme-in-gtk-windows)。 –

1

舊線程我知道,但一個模型可能會將渲染的表面複製到另一個緩衝區,然後重新繪製無效區域。 Julia語言技術的一個例子可以在here找到。在實踐中,這似乎取得了非常好的表現。