null

Делаем отрисовку плавной в PyGTK + Cairo

  Я уже писал об использовании PyGTK и Cairo для создания собственого векторного графического редактора. К сожалению, при большом количестве объектов отрисовка не выглядит плавной, что крайне неприятно. Рассмотрим, как исправить эту проблему.

  Буферизация изображения перед отрисовкой

  Одна из причин мельканий заключается в том, что мы каждый раз перерисовываем все объекты на графическом контексте. Вместо этого можно рисовать сначала в буфер ImageSurface, а затем отрисовывать его на графическом контексте виджета:

surface = cairo.ImageSurface(cairo.FORMAT_RGB24, self.rect.width, self.rect.height)
  scr = cairo.Context(surface)
 
  # Рисуем на scr...
 
  cr.set_source(cairo.SurfacePattern(surface))
  cr.paint()

  Кроме того можно использовать два буфера: один со всеми элементами кроме редактируемого
 
  Хотя это и решило проблему с "мельканиями", более явной стала другая - изменение размера прямоугольника не всегда поспевает за движениями мыши пользователя. Связано это с тем, что обработчик события motion-event (движение мышью) каждый раз генерирует событие expose, и пока не будет перерисован контекст. Выход из этой ситуации - использовать отдельный поток для обработки motion_event.

Добавляем потоки 

  Перед вызовом gtk.main() нужно вызвать threads_init, а сам вызов обернуть в соответствующие функции:

def main(self):
        self.win.show_all()
        
        gtk.threads_init()
        
        try:
            gtk.threads_enter()
            gtk.main()
            gtk.threads_leave()
        finally:
            self.drawing_area.stop_threads()
При использовании потоков не следует совместно использовать объекты между потоками (например описания событий, передаваемые в обработчик). Кроме того, pygtk должен быть собран с опцией --enable-threads

    Все обращения к gtk из внешних потоков также должны быть обернуты в thread_enter и threads_leave. Теперь создадим поток, а для передачи сообщений ему будем использовать синхронную очередь Queue:

def __init__(self):
        ...
        # Создаем и запускаем поток
        self.motion_thr = threading.Thread(target = self.motion_thread)
        self.motion_queue = Queue()
        self.motion_thr_running = True        
        self.motion_thr.start()
    
    def stop_threads(self):
    # Посылаем в очередь пустые объекты, чтобы
    # выйти из блокирующего get() и завершаем поток
    self.motion_thr_running = False
    
        self.motion_queue.put((None, None, None))
    
    def on_motion(self, widget, event):
        if self.cur_element is None:
            return
        
        is_shift = event.state & gtk.gdk.SHIFT_MASK
        self.motion_queue.put((event.x, event.y, is_shift))
    
    def motion_thread(self):
        proc_events = False
        proc_time = time.time()
        
        while self.motion_thr_running:
            if proc_events and (time.time() - proc_time) > 0.01:
        # Если есть хотя бы одно событие (proc_events) и они
        # накапливались в течение 10 мс, обработать последнее и
        # перерисовать область
                self.async_motion(x, y, is_shift)
                
                # Перерисовываем область
                gtk.threads_enter()
                area = self.cur_element.get_area() \
                    if self.cur_element is not None \
                    else None
                self.redraw(area)
                gtk.threads_leave()
            
                proc_events = False
                proc_time = time.time()
            
            try:
                if self.cur_element is None:
                    # Опрашиваем очередь, чтобы получить новое событие
                    # Если в течение 5 мс не появится новое, разблокируемся
                    x, y, is_shift = self.motion_queue.get(False, 0.005)
                else:
                    # Блокируемся пока не получим первое событие
                    x, y, is_shift = self.motion_queue.get()
            except Empty:
                continue
                        
            proc_events = True
    
    def async_motion(self, x, y, is_shift):
    # Собственно обработчик события
    ...



    Теперь вместо того, чтобы сразу инициировать событие expose, поток motion_thread будет ждать не менее чем 10 мс:

Скачать обновленный пример можно тут: pygtk-cairo-0.2.zip (13,9 Кб)

Ссылки

Делаем свой виджет с помощью PyGTK и Cairo
Использование модуля threading в Python
PyGTK FAQ: I am using a separate thread to run my code, but the application (or the UI) hangs.

К списку статей

 

Интересуюсь по большей части системным анализом программного обеспечения: поиском багов и анализом неисправностей, а также системным программированием (и не оставляю надежд запилить свою операционку, хотя нехватка времени сказывается :) ). Программированием увлекаюсь с 12 лет, но так уж получилось, что стал я инженером.

Основная сфера моей деятельности связана с поддержкой Solaris и оборудования Sun/Oracle, хотя в последнее время к ним прибавились технологии виртуализации (линейка Citrix Xen) и всякое разное от IBM - от xSeries до Power. Учусь на кафедре Вычислительной Техники НИУ ИТМО.

See you...out there!

http://www.facebook.com/profile.php?id=100001947776045
https://twitter.com/AnnoyingBugs