Журнал LinuxFormat - перейти на главную

LXF131:Clutter

Материал из Linuxformat
Перейти к: навигация, поиск

Python Настоящие задачи, чтобы вы поупражнялись в кодировании

Содержание

Python: Рисуем с Clutter

Clutter предоставляет интерфейс, но, опираясь на свой опыт в графике, Ник Вейч добавит к нему мощь Cairo и создаст сетевые часы.

Мы уже видели, как создавать в Clutter простые объекты, размещать их на экране и даже анимировать. Но пока мы создавали при помощи Clutter одни прямоугольнички. Эй! А как же другие формы и поверхности? Не-е, только не в Clutter. Но голь на выдумки хитра: Clutter запряг вместо себя библиотеки Cairo. Они содержат дюжину операций рисования в текстурах, и мы можем создать контекст Cairo и воспользоваться ими. Когда Cairo выполнит сложную часть, Clutter применит результат, как любую другую текстуру.

В ранних версиях Clutter поддержка Cairo была реализована в виде встраиваемого модуля. Теперь это часть основного кода Clutter, и нам не придется импортировать какие-либо специальные библиотеки – хотя для работы с контекстами Cairo потребуется Python-модуль Cairo. Откройте интерактивную оболочку Python, введя python в командной строке, и наберите следующее:

 >>> import clutter
 >>> import cairo
 >>> import math

Если вы планируете работать с окружностями, дугами и так далее, имеет смысл импортировать модуль Math из стандартного комплекта Python: в нем имеется константа пи (math.pi) и методы для преобразования радиан в градусы и обратно. Далее выполним стандартный ритуал настройки сцены Clutter:

 >>> stage = clutter.Stage()
 >>> stage.set_size(500,500)
 >>> blue =clutter.Color(0,0,255,255)
 >>> stage.set_color(blue)

Если вы следили за серией уроков по Clutter, то понимаете, что это обычные действия, выполняемые при подготовке сцены. Теперь создадим нашего актера.

 >>> cairoactor=clutter.CairoTexture(50,50)
 >>> cairoactor.set_position(225,225)

Здесь генерируется специальный актер, получающий текстуру из Cairo, и мы размещаем его слева от центра нашей сцены, с учетом размера круга. А теперь нарисуем объект.

 >>> context=cairoactor.cairo_create()
 >>> context.set_operator(cairo.OPERATOR_OVER)
 >>> context.set_source_rgb(1,1,0)
 >>> context.arc(25,25,20,0, 2*math.pi)
 >>> context.close_path()
 >>> context.fill()

Объект context, созданный нами здесь, на самом деле является измененным объектом Cairo и наследует все полезные методы Cairo. Прежде чем начинать любые операции рисования, надо настроить режим рисования. Чаще всего мы будем использовать операцию OVER, которая рисует поверх всего, что уже имеется в текстуре. Set_source_rgb() устанавливает цвет пера для будущих операций. Затем изобразим наш круг.

Круг, в терминах Cairo, это просто дуга, простирающаяся на 360° (или 2π, поскольку Cairo измеряет углы в радианах). Для этого метода мы должны указать координаты X и Y центральной точки, затем радиус дуги, а также начальный и конечный углы в радианах. Мы указали от 0 до 2*math.pi, то есть 360°.

Заливаемый контур должны быть замкнутым. Вы можете подумать, что он уже замкнут – а вот и нет. По мнению Cairo, это пока просто контур, который начинается и заканчивается в одной точке.

Чтобы замкнуть его, следует вызвать метод close_path(), а уж потом можно закрашивать. Метод fill() пытается заполнить текущую активную фигуру. Если же мы хотим изобразить контур фигуры (то есть окружность, а не круг), следует использовать метод stroke().

Создав круг, отобразим этот объект, добавим его в сцену и затем отрисуем все вместе:

 >>> cairoactor.show()
 >>> stage.add(cairoactor)
 >>> stage.show_all()

Ну и где круг?! Мы же ввели команды рисования, а затем добавили актера на сцену! Ответ в том, что мы покамест в контексте Cairo. Модуль Cairo не приступит к выводу изображения вашей текстуры, пока вы его не удалите. Да, это звучит странно, но по пробуйте:

 >>> del context

Что такое Cairo?

Не считая родного города пирамид, Cairo – это название графического инструментария, созданного Карлом Вортом [Carl Worth] и другими. Мы брали у его разработчиков интервью в LXF111.

Целью Cairo было создание стандартного набора графических инструментов для выполнения насущных задач быстро и просто. Во многих приложениях, где надо отрисовывать графику, реализуются свои собственные алгоритмы – в уверенности, что они будут быстрее, лучше и проще в сопровождении; но часто это не так.

Cairo кросс-платформенный, интегрируется с нескольким графическими оболочками и поддерживает графическое ускорение. Дополнительную информацию см. на http://www.cairographics.org.

Как это работает

Как по волшебству, круг появился! Запутаться немудрено, но представьте себе такую модель. Пусть Clutter – директор картинной галереи. Галерея – то место, где объекты отображаются; но создаются они в другом месте. Галерея (Clutter) берет холст (текстуру) и отправляет его художнику (Cairo). Затем галерея просит художника нарисовать картину в соответствии с инструкциями (вызовы в контексте). Но чтобы галерея могла отобразить ее, художник должен отправить картину обратно (удалить из контекста). Если галерея когда-либо захочет изменить картину, она может просто вернуть ее художнику (создать для объекта новый контекст). Так что воспринимайте удаление объекта контекста как завершение контракта.

Есть и другие моменты, о которых следует знать при работе с кодом Clutter/Cairo, особенно в Python. Текстура обновляется, когда контекст выходит за границы области видимости (в коде). В предыдущем примере это происходило потому, что мы явным образом удаляли его, а Python очень хорошо очищает память, которая больше не требуется. Также, контекст может выйти из области видимости и автоматически удалиться, если, например, это локальная переменная в завершившейся функции. За этим необходимо следить. Некоторые программисты используют эту особенность, чтобы прибирать за собой – ведь если сборщик мусора Python желает сам заботиться об удалении ваших объектов, то чего напрягаться? Но иногда наличие явных удалений облегчает понимание кода. К тому же для некоторых библиотек, несмотря на то, что Python освобождает память, модуль может не освободить дескриптор контекста, что вызовет проблемы.

Большинство операторов рисования Cairo имеют альтернативы, использующие относительные координаты вместо абсолютных. И это замечательно, когда объект рисуется и точно не известно, где он начинается, или известно лишь, что необходимо перейти в определенном направлении на заданное расстояние (к сожалению, сложение векторов среди умений Cairo не значится).

Чтобы разобраться со всем этим, изобразим треугольник. Помните, что координаты, передаваемые методам context, задаются относительно текстуры Cairo, а не сцены.

 >>> import clutter
 >>> import cairo
 >>> stage = clutter.Stage()
 >>> stage.set_size(500,500)
 >>> blue =clutter.Color(0,0,255,255) 
 >>> stage.set_color(blue)
 >>> cairoactor=clutter.CairoTexture(50,50)
 >>> cairoactor.set_position(225,225)
 >>> context=cairoactor.cairo_create()
 >>> context.set_operator(cairo.OPERATOR_OVER)
 >>> context.set_line_width(5)
 >>> context.set_source_rgb(1,0,0)
 >>> context.move_to(3,3)
 >>> context.line_to(47,3)
 >>> context.rel_line_to(-22,44)
 >>> context.close_path()
 >>> context.stroke_preserve()
 >>> context.set_source_rgb(0,1,0)
 >>> context.fill()
 >>> del context
 >>> cairoactor.show()
 >>> stage.add(cairoactor)
 >>> stage.show_all()


Поскольку на сей раз мы применили операцию stroke, необходимо установить толщину линии. Кроме того, мы использовали метод stroke_preserve(). Опцию preserve имеют большинство методов рисования. Обычно их вызов удаляет активный контур в ходе стандартной процедуры очистки текстуры. Если требуется ис пользовать контур вновь, сохраните его для следующей операции опцией preserve. Если запутались с координатами, смотрите рисунок.

Рисуем часы

Для наших часов нарисуем циферблат при помощи объектов Cairo, затем создадим несколько текстур для стрелок. Стрелки можно сделать любой сложности. В нашем проекте мы применим вытянутые ромбы, поскольку сложные стрелки требуют больше кода для рисования и отнимают место на объяснение происходящего.

Циферблат часов – это текстура, но в ней будут повторяющиеся по кругу объекты, обозначающие точки циферблата. Можно было заняться математикой и в цикле разместить каждую цифру в виде отдельной текстуры. Но это утомительно и занимает много места; вместо этого создадим их программно. ' Облегчим себе жизнь некоторыми преобразованиями из Cairo. Метод translate() смещает центральную ось контекста области рисования, а rotate() вращает ее. Скомбинировав их, мы можем повторно рисовать один и тот же круг и сдвигать контекст между отрисовками – воспринимайте это как печать на листе бумаги, который сдвигается перед очередным оттиском. Штамп опускается на то же место, а поверхность смещается. Мы разбили код на разделы, чтобы вы могли видеть это в действии, начиная с def_drawface.

Запускаем часы

Данный код создает объект класса clock, содержащий все необходимые причиндалы. Процедура init настраивает сцену, вызывает функции для отрисовки необходимых текстур и реализует шкалу времени. Это та шкала времени, что заставляет наши часы тикать – она обновляет их каждые 1000 мс. Отметим, что метод update обращается к системному времени, не полагаясь только на прерывания, так что часы всегда синхронизированы с системными.


 import clutter
 import cairo
 import math
 import time
 class clock:
   def __init__ (self):
     self.stage = clutter.Stage()
     self.stage.set_color(clutter.Color(0, 0, 0, 255))
     self.stage.set_size(500, 500)
     self.stage.set_title(‘LXF Atomic Clock’)
     self.stage.connect(‘key-press-event’, self.parseKeyPress)
     self.stage.connect(“destroy”, clutter.main_quit)
     self.drawface()
     self.drawhands()
     self.stage.show_all()
     self.t=clutter.Timeline()
     self.t.set_duration(1000)
     self.t.set_loop(True)
     self.t.connect(‘completed’, self.update)
     self.t.start()
     clutter.main()
   def parseKeyPress(self, stage, event):
     # Выполняем действия по на жатию клавиши пользователем
     if event.keyval == clutter.keysyms.q:
        # Ес ли на жата клавиша “q” - вы ходим из при ложения
        clutter.main_quit()
   def drawface(self):
     self.face=clutter.CairoTexture(500,500)
     self.face.set_position(0,0)
     context=self.face.cairo_create()
     context.set_operator(cairo.OPERATOR_OVER)
     context.set_source_rgb(1,1,0)
     context.translate(250, 250)
     for a in range(12):
        context.arc(0,230,12,0, 2*math.pi)
        context.close_path()
        context.fill()
        context.rotate(2*math.pi/12)
    for a in range(4):
        context.arc(0,230,18,0, 2*math.pi)
        context.close_path()
        context.fill()
        context.rotate(2*math.pi/4)
     del context
     self.face.show_all()
     self.stage.add(self.face)
   def drawhands(self):
     self.hhand=clutter.CairoTexture(30,220)
     self.hhand.set_position(235,250)
     context=self.hhand.cairo_create()
     context.set_operator(cairo.OPERATOR_OVER)
     context.set_source_rgb(1,1,1)
     context.set_line_width(2)
      context.move_to(0,0)
      context.line_to(30,0)
      context.line_to(25,120)
      context.line_to(5, 120)
      context.close_path()
      context.stroke_preserve()
      context.set_source_rgb(0,1,0)
      context.fill()
      del context
      self.hhand.show_all()
      self.stage.add(self.hhand)
      # минут ная стрелка
      self.mhand=clutter.CairoTexture(16,220)
      self.mhand.set_position(242,250)
      context=self.mhand.cairo_create()
      context.set_operator(cairo.OPERATOR_OVER)
      context.set_source_rgb(1,1,1)
      context.set_line_width(2)
      context.move_to(0,0)
      context.line_to(16,0)
      context.line_to(14,160)
      context.line_to(2, 160)
      context.close_path()
      context.stroke_preserve()
      context.set_source_rgb(0,0,1)
      context.fill()
      del context
      self.mhand.show_all()
      self.stage.add(self.mhand)
      # часовая стрелка
      self.shand=clutter.CairoTexture(9,225)
      self.shand.set_position(246,250)
      context=self.shand.cairo_create()
      context.set_operator(cairo.OPERATOR_OVER)
      context.set_source_rgb(1,1,1)
      context.set_line_width(1)
      context.move_to(0,0)
      context.line_to(9,0)
      context.line_to(5,225)
      context.close_path()
      context.stroke_preserve()
      context.set_source_rgb(1,0,0)
      context.fill()
      del context
      self.shand.show_all()
      self.stage.add(self.shand)
    def update(self, signal):
      z=time.gmtime()
      hangle=30*z.tm_hour+(.5*z.tm_min)-180
      mangle=6*z.tm_min+(.1*z.tm_sec)-180
      sangle=6*z.tm_sec-180
      self.hhand.set_rotation(clutter.Z_AXIS, hangle, 15, 0, 0)
      self.mhand.set_rotation(clutter.Z_AXIS, mangle, 8, 0, 0)
      self.shand.set_rotation(clutter.Z_AXIS, sangle, 5, 0, 0)
 if __name__ == ‘__main__’:
    x=clock()

Львиная доля кода уйдет на стрелки часов, ведь для каждой черточки нужна своя линия. Можно сделать текстуры сколь угодно сложными, если, конечно, не жаль времени на написание рисующего их кода.

Изначально мы собирались корректировать системное время при помощи часов NTP в Интернете. Увы, это оказалось малость непрактично – в современных дистрибутивах настроить время из скрипта Python не так-то просто: обычно необходимы привилегии root, а давать их пользовательскому сценарию не здорово!

Поэкспериментируем с этой идеей: например, можно периодически получать время с сервера NTP. Шкала времени Clutter может использоваться как прерывание, генерируемое, скажем, каждый час. Используя модуль Python ntplib (http://pypi.python.org/pypi/ntplib), можно сравнить системное время с «реальным» и получить смещение, которое будет применено к часам после очередного вызова метода update(). Вы можете считать свои системные часы очень точными, но они быстро наработают отклонение, особенно на мобильных устройствах. Вот как работает механизм смещения:


 def ntpcheck(self, signal):
  client=ntplib.NTPClient()
  response=client.request(‘europe.pool.ntp.org’,version=3)
  realtime= time.gmtime(response.tx_time)
  systime=time.gmtime()
  self.offset=(realtime.tm_hour-systime.tm_hour,realtime.tm_min-systime.tm_min,realtime.tm_sec-systime.tm_sec)

Другое возможное дополнение – анимация хода часов. Если честно, они передвигаются на такие короткие расстояния, что многие люди не заметят разницы; но было бы здорово применить одну из «прыгающих» анимаций для эмуляции перемещения стрелки, перескакивающей на новое место, как в старинных часах.

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

Важное замечание о версиях

Наш урок основан на релизе 1.0 библиотеки Clutter и 0.98 – модуля PyClutter. Более поздние версии поладят с кодом нашего урока, а более ранние – не факт: кроме всего прочего, структура пространства имен в PyClutter несколько раз менялась, как и иерархия объектов Clutter.

Всегда желательно по возможности брать библиотеку Clutter и модуль Python из репозитория вашего дистрибутива, если там достаточно новые версии.

Персональные инструменты
купить
подписаться
Яндекс.Метрика